merge: merge dev

* fix: 登录加密

* fix: 登录加密功能

* fix: 树样式错乱bug修改

* fix: bug#17059

* fix: bug#17128

* fix: 17131

* fix: bug#17122

* fix: bug#17156

* fix: 修改bug

* fix: bug#17056

* fix: bug#17005

* fix: 修改bug

* fix: bug#17296

* fix: bug应用管理/集成菜单

* fix: bug#17299

* fix: bug#17148

* fix: bug#17392

* fix: bug#17416

* fix: bug#17417

* fix: bug#17418

* fix: bug#17421

* fix: bug#17556、17555

* fix: bug#17299

* fix: bug#17148

* fix: bug#17392

* fix: bug#17416

* fix: bug#17417

* fix: bug#17418

* fix: bug#17421

* fix: bug#17510

* fix: bug#17510

* fix: bug#17299

* fix: 修改tree样式

* fix: 修改样式

* fix: bug#17425

* fix: 跳转bug

* fix: bug#17556、17555

* fix: 资产分配权限为空问题

* fix: 资产分配权限为空问题

* fix: bug#17557、17299

* fix: bug#17537,修复设备详情映射bug,修复数采仪表盘bug

* fix: 资产分配权限为空问题

* fix: bug#17557、17299

* fix: 修改bug

* fix: bug#17148

* fix: bug#17564

* fix: 数采仪表盘bug

* fix: bug#17537

* fix: 设备详情bug

* fix: 网关设备-远程控制兼容https

* fix: bug网关设备跳转

* fix: bug网关设备跳转

* fix: 修复组织管理树编辑携带多余参数

* fix: 组织管理树提交bug

* fix: 修复404页面

* fix: bug#17584

* fix: 组织管理树提交bug

* fix: bug#17584
This commit is contained in:
XieYongHong 2023-08-18 18:07:34 +08:00 committed by GitHub
parent dd1d9ccbe6
commit 9e7e63f713
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 766 additions and 176 deletions

View File

@ -4,6 +4,13 @@ import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable'
import { DeviceInstance } from '@/views/device/Instance/typings' import { DeviceInstance } from '@/views/device/Instance/typings'
import { DeviceMetadata, UnitType } from '@/views/device/Product/typings'; import { DeviceMetadata, UnitType } from '@/views/device/Product/typings';
/**
*
* @param deviceId ID
* @param productId ID
*/
export const resetRule = (productId:string,deviceId:string,data:any) => server.remove(`/virtual/property/product/${productId}/${deviceId}/_batch`,{},{data})
/** /**
* *
* @param deviceId ID * @param deviceId ID

View File

@ -10,6 +10,11 @@ export const getProductList = (parmas?:any) => server.get('/device/product/_qu
*/ */
export const getDeviceList = (parmas?:any) => server.get('/device-instance/_query/no-paging?paging=false',parmas); export const getDeviceList = (parmas?:any) => server.get('/device-instance/_query/no-paging?paging=false',parmas);
/**
*
*/
export const getAlarmProduct = (parmas:any) => server.post('/device-instance/_query',parmas)
/** /**
* *
*/ */

View File

@ -38,7 +38,7 @@ router.beforeEach((to, from, next) => {
router.addRoute('base',{ router.addRoute('base',{
path: '/:pathMatch(.*)', path: '/:pathMatch(.*)',
name: 'error', name: 'error',
component: () => NotFindPage component: NotFindPage
}) })
next({ ...to, replace: true }) next({ ...to, replace: true })

View File

@ -61,22 +61,21 @@ const data: any = ref({
const pickerTimeChange = () => { const pickerTimeChange = () => {
data.value.time.type = undefined; data.value.time.type = undefined;
console.log(1);
}; };
const getEcharts = async (val: any) => { const getEcharts = async (val: any) => {
loading.value = true; loading.value = true;
const resp: any = await dashboard(pointParams(val)); const resp: any = await dashboard(pointParams(val));
if (resp.success) { if (resp.success && resp?.result?.length) {
const x = resp.result const x = resp.result
.map((item: any) => item.data.timeString) .map((item: any) => item.data.timeString)
.reverse(); .reverse();
const y = resp.result.map((item: any) => item.data.value).reverse(); const y = resp.result.map((item: any) => item.data.value).reverse();
handleOptions(x, y); handleOptions(x, y);
} }
setTimeout(() => { setTimeout(()=>{
loading.value = false; loading.value = false;
}, 300); },300)
}; };
const handleOptions = (x = [], y = []) => { const handleOptions = (x = [], y = []) => {

View File

@ -129,8 +129,9 @@ import { TOKEN_KEY } from '@/utils/variable'
import { Form } from 'ant-design-vue' import { Form } from 'ant-design-vue'
import { applicationInfo, bindAccount } from '@/api/bind' import { applicationInfo, bindAccount } from '@/api/bind'
import { code, authLogin, userDetail } from '@/api/login' import { code, authLogin, userDetail , authLoginConfig} from '@/api/login'
import { useSystem } from '@/store/system' import { useSystem } from '@/store/system'
import {encrypt} from '@/utils/encrypt'
const useForm = Form.useForm; const useForm = Form.useForm;
const systemStore = useSystem(); const systemStore = useSystem();
@ -175,15 +176,17 @@ const getUrlCode = () => {
const bindUser = ref<any>({ appName: '' }) const bindUser = ref<any>({ appName: '' })
const getAppInfo = async () => { const getAppInfo = async () => {
const code = getUrlCode() const code = getUrlCode()
const { result } = await applicationInfo(code) const { result,success } = await applicationInfo(code)
bindUser.value = result bindUser.value = result || {}
if (result.applicationProvider === 'dingtalk-ent-app') { if(success){
bindUser.value.appName = '钉钉' if (result?.applicationProvider === 'dingtalk-ent-app') {
} else if (result.applicationProvider === 'wechat-webapp') { bindUser.value.appName = '钉钉'
bindUser.value.appName = '微信' } else if (result?.applicationProvider === 'wechat-webapp') {
} else { bindUser.value.appName = '微信'
bindUser.value.appName = result.applicationName } else {
bindUser.value.appName = result?.applicationName
}
} }
} }
@ -239,7 +242,11 @@ const getCode = async () => {
captcha.value.key = res.result?.key captcha.value.key = res.result?.key
} }
const RsaConfig = reactive<any>({
enabled:false, //
publicKey:'', //rsa,使
id:'' //ID
})
/** /**
* 登录并绑定账户 * 登录并绑定账户
*/ */
@ -252,14 +259,25 @@ const handleLoginBind = () => {
bindCode: code, bindCode: code,
expires: 3600000 expires: 3600000
} }
const resq:any = await authLoginConfig()
if(resq.status === 200){
if(resq.result?.encrypt){
RsaConfig.enabled = resq.result?.encrypt.enabled
RsaConfig.publicKey = resq.result?.encrypt.publicKey
RsaConfig.id = resq.result?.encrypt.id
}
}
if (captcha.value.base64) { if (captcha.value.base64) {
params.verifyKey = captcha.value.key params.verifyKey = captcha.value.key
} else { } else {
delete params.verifyCode delete params.verifyCode
} }
const data = {
const res = await authLogin(params) ...params,
password:RsaConfig.enabled?encrypt(params.password,RsaConfig.publicKey):params.password,
encryptId:RsaConfig.enabled?RsaConfig.id:undefined
}
const res = await authLogin(data)
console.log('res: ', res) console.log('res: ', res)
if (res.success) { if (res.success) {
onlyMessage('登录成功') onlyMessage('登录成功')
@ -273,11 +291,26 @@ const handleLoginBind = () => {
}) })
} }
const getQueryVariable = (): Map<string, string> => {
const index = window.location.href.indexOf('?')
const paramsUrl = window.location.href.substr(index + 1)
const paramsArr = paramsUrl.split('#')?.[0] || ''
const vars = paramsArr.split('&');
const maps = new Map()
for (let i = 0; i < vars.length; i++) {
const pair = vars[i].split('=');
const [key, value] = pair
maps.set(key, value)
}
return maps;
}
/** /**
* 绑定成功跳转至页面url的: redirect * 绑定成功跳转至页面url的: redirect
*/ */
const goRedirect = () => { const goRedirect = () => {
const urlParams = new URLSearchParams(window.location.hash) const urlParams = getQueryVariable();
const redirectUrl = const redirectUrl =
urlParams.get('redirect') || urlParams.get('redirect') ||
window.location.href.split('redirect=')?.[1] window.location.href.split('redirect=')?.[1]

View File

@ -21,8 +21,6 @@
: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

@ -8,10 +8,13 @@
<template #bodyCell="{ column, text, record, index }"> <template #bodyCell="{ column, text, record, index }">
<div> <div>
<template <template
v-if="['valueType', 'name'].includes(column.dataIndex)" v-if="['name'].includes(column.dataIndex)"
> >
<span>{{ text }}</span> <span>{{ text }}</span>
</template> </template>
<template v-else-if="['valueType'].includes(column.dataIndex)">
<span>{{ text.type }}</span>
</template>
<template v-else> <template v-else>
<j-form-item <j-form-item
:name="['dataSource', index, 'value']" :name="['dataSource', index, 'value']"
@ -24,19 +27,19 @@
> >
<ValueItem <ValueItem
v-model:modelValue="record.value" v-model:modelValue="record.value"
:itemType="record.type" :itemType="record.valueType.type"
:options=" :options="
record.type === 'enum' record.valueType.type === 'enum'
? ( ? (
record?.dataType?.elements || [] record?.valueType?.elements || []
).map((item) => { ).map((item) => {
return { return {
label: item.text, label: item.text,
value: item.value, value: item.value,
}; };
}) })
: record.type === 'boolean' : record.valueType.type === 'boolean'
? [ ? [
{ label: '是', value: true }, { label: '是', value: true },
{ label: '否', value: false }, { label: '否', value: false },
@ -94,6 +97,7 @@ const formRef = ref<any>(null);
watchEffect(() => { watchEffect(() => {
modelRef.dataSource = _props?.modelValue || [] modelRef.dataSource = _props?.modelValue || []
console.log(modelRef.dataSource);
}) })
const onSave = () => const onSave = () =>

View File

@ -170,7 +170,7 @@ const funcChange = (val: string) => {
id: item.id, id: item.id,
name: item.name, name: item.name,
value: undefined, value: undefined,
valueType: item?.valueType?.type, valueType: item?.valueType,
required: item?.expands?.required, required: item?.expands?.required,
}; };
}); });

View File

@ -21,8 +21,6 @@
: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

@ -33,6 +33,10 @@ const props = defineProps({
id: { id: {
type: String, type: String,
default: undefined default: undefined
},
pluginId:{
type:String,
default: undefined
} }
}) })
@ -46,7 +50,7 @@ const handleOk = async () => {
loading.value = true loading.value = true
const res = await savePluginData( const res = await savePluginData(
'device', 'device',
props.accessId!, props.pluginId!,
route.params.id as string, route.params.id as string,
checkKey.value checkKey.value
).catch(() => ({ success: false })) ).catch(() => ({ success: false }))

View File

@ -103,6 +103,7 @@
v-if='inkingVisible' v-if='inkingVisible'
:id='inklingDeviceId' :id='inklingDeviceId'
:accessId='instanceStore.current.accessId' :accessId='instanceStore.current.accessId'
:pluginId="channelId"
@cancel="inkingVisible = false" @cancel="inkingVisible = false"
@submit='saveInkling' @submit='saveInkling'
/> />
@ -150,7 +151,7 @@ const queryInkling = () => {
queryPluginAccessDetail(instanceStore.current?.accessId).then(async res => { queryPluginAccessDetail(instanceStore.current?.accessId).then(async res => {
if (res.success) { if (res.success) {
channelId.value = res.result.channelId channelId.value = res.result.channelId
const pluginRes = await getPluginData('device',instanceStore.current?.accessId, instanceStore.current?.id) const pluginRes = await getPluginData('device',channelId.value, instanceStore.current?.id)
if (pluginRes.success) { if (pluginRes.success) {
inklingDeviceId.value = pluginRes.result?.externalId inklingDeviceId.value = pluginRes.result?.externalId
} }

View File

@ -56,7 +56,24 @@
{{ TypeStringMap[data.record.valueType?.type] }} {{ TypeStringMap[data.record.valueType?.type] }}
</template> </template>
<template #inputs="{ data }"> <template #inputs="{ data }">
<InputParams v-model:value="data.record.inputs" /> <j-tooltip
v-if="target === 'device' && productNoEdit.id?.includes?.(data.record.id)"
title="继承自产品物模型的数据不支持修改"
>
<!-- <ModelButton :disabled="true"/>-->
<j-button :disabled="true" type="link" style="padding-left: 0;">
<AIcon type="SettingOutlined" />
配置
</j-button>
</j-tooltip>
<PermissionButton
v-else
:has-permission="`${permission}:update`"
type="link"
key="inputs"
>
<InputParams v-model:value="data.record.inputs" />
</PermissionButton>
</template> </template>
<template #output="{ data }"> <template #output="{ data }">
{{ data.record.output?.type }} {{ data.record.output?.type }}
@ -71,7 +88,24 @@
{{ data.record.id && !data.record?.expands?.source ? '设备' : sourceMap?.[data.record?.expands?.source] || '' }} {{ data.record.id && !data.record?.expands?.source ? '设备' : sourceMap?.[data.record?.expands?.source] || '' }}
</template> </template>
<template #properties="{ data }"> <template #properties="{ data }">
<ConfigParams v-model:value="data.record.valueType" /> <j-tooltip
v-if="target === 'device' && productNoEdit.id?.includes?.(data.record.id)"
title="继承自产品物模型的数据不支持修改"
>
<!-- <ModelButton :disabled="true"/>-->
<j-button :disabled="true" type="link" style="padding-left: 0;">
<AIcon type="SettingOutlined" />
配置
</j-button>
</j-tooltip>
<PermissionButton
v-else
:has-permission="`${permission}:update`"
type="link"
key="properties"
>
<ConfigParams v-model:value="data.record.valueType" />
</PermissionButton>
</template> </template>
<template #outInput> <template #outInput>
object object
@ -92,8 +126,13 @@
配置 配置
</j-button> </j-button>
</j-tooltip> </j-tooltip>
<OtherSetting <PermissionButton
v-else v-else
:has-permission="`${permission}:update`"
type="link"
key="setting"
>
<OtherSetting
v-model:value="data.record.expands" v-model:value="data.record.expands"
:id="data.record.id" :id="data.record.id"
:disabled="target === 'device' && productNoEdit.id?.includes?.(data.record.id)" :disabled="target === 'device' && productNoEdit.id?.includes?.(data.record.id)"
@ -103,11 +142,13 @@
} : undefined" } : undefined"
:type="data.record.valueType.type" :type="data.record.valueType.type"
/> />
</PermissionButton>
</template> </template>
<template #action="{data}"> <template #action="{data}">
<j-space> <j-space>
<PermissionButton <PermissionButton
:has-permission="`${permission}:add`" :has-permission="`${permission}:update`"
type="link" type="link"
key="edit" key="edit"
style="padding: 0" style="padding: 0"
@ -148,7 +189,7 @@
<AIcon type="FileSearchOutlined" /> <AIcon type="FileSearchOutlined" />
</PermissionButton> </PermissionButton>
<PermissionButton <PermissionButton
:has-permission="`${permission}:delete`" :has-permission="`${permission}:update`"
type="link" type="link"
key="delete" key="delete"
style="padding: 0" style="padding: 0"
@ -228,6 +269,7 @@ import {cloneDeep} from "lodash";
import {useSystem} from "store/system"; import {useSystem} from "store/system";
import {storeToRefs} from "pinia"; import {storeToRefs} from "pinia";
import { FULL_CODE } from 'jetlinks-ui-components/es/DataTable' import { FULL_CODE } from 'jetlinks-ui-components/es/DataTable'
import { usePermissionStore } from '@/store/permission';
const props = defineProps({ const props = defineProps({
target: { target: {
@ -253,6 +295,7 @@ const router = useRouter()
const { data: metadata, noEdit, productNoEdit } = useMetadata(_target, props.type); const { data: metadata, noEdit, productNoEdit } = useMetadata(_target, props.type);
const { hasOperate } = useOperateLimits(_target); const { hasOperate } = useOperateLimits(_target);
const permissionStore = usePermissionStore()
const metadataStore = useMetadataStore() const metadataStore = useMetadataStore()
const instanceStore = useInstanceStore() const instanceStore = useInstanceStore()
const productStore = useProductStore() const productStore = useProductStore()
@ -467,7 +510,7 @@ const handleSaveClick = async (next?: Function) => {
const tabsChange = inject('tabsChange') const tabsChange = inject('tabsChange')
const parentTabsChange = (next?: Function) => { const parentTabsChange = (next?: Function) => {
if (editStatus.value) { if (editStatus.value && permissionStore.hasPermission(`${props.permission}:update`)) {
const modal = Modal.confirm({ const modal = Modal.confirm({
content: '页面改动数据未保存', content: '页面改动数据未保存',
okText: '保存', okText: '保存',

View File

@ -198,6 +198,12 @@ export const useColumns = (type?: MetadataType, target?: 'device' | 'product', n
}, },
{ max: 64, message: '最多可输入64个字符' }, { max: 64, message: '最多可输入64个字符' },
] ]
// rules:[{
// callback(rule:any,value: any, dataSource: any[]) {
// console.log(rule,value,dataSource,123)
// return value
// }
// }]
}, },
doubleClick(record) { doubleClick(record) {
if (isExtendsProduct(record.id, productNoEdit?.value, 'name')) { if (isExtendsProduct(record.id, productNoEdit?.value, 'name')) {
@ -343,9 +349,8 @@ export const useColumns = (type?: MetadataType, target?: 'device' | 'product', n
callback(rule:any,value: any, dataSource: any[]) { callback(rule:any,value: any, dataSource: any[]) {
const field = rule.field.split('.') const field = rule.field.split('.')
const fieldIndex = Number(field[1]) const fieldIndex = Number(field[1])
const values = dataSource.find((item, index) => index === fieldIndex) const values = dataSource.find((item, index) => index === fieldIndex)
return validatorConfig(values.output) return validatorConfig(values?.output)
} }
}] }]
}, },
@ -420,6 +425,7 @@ export const useColumns = (type?: MetadataType, target?: 'device' | 'product', n
props: { props: {
noEdit: noEdit?.value?.source || [], noEdit: noEdit?.value?.source || [],
target: target, target: target,
productNoEdit: productNoEdit?.value
} }
}, },
doubleClick(record){ doubleClick(record){

View File

@ -7,13 +7,13 @@
v-if="type === 'array'" v-if="type === 'array'"
v-model:value="_valueType.elementType" v-model:value="_valueType.elementType"
:unitOptions="unitOptions" :unitOptions="unitOptions"
placement="topRight" placement="bottomRight"
@confirm="(data) => {valueChange(data, 'array')}" @confirm="(data) => {valueChange(data, 'array')}"
/> />
<DataTableObject <DataTableObject
v-else-if="type === 'object'" v-else-if="type === 'object'"
:value="_valueType.properties" :value="_valueType.properties"
placement="topRight" placement="bottomRight"
:columns="columns" :columns="columns"
@confirm="(data) => {valueChange(data, 'object')}" @confirm="(data) => {valueChange(data, 'object')}"
> >
@ -25,28 +25,28 @@
<ConfigModal v-model:value="data.record.valueType" :showOther="false" /> <ConfigModal v-model:value="data.record.valueType" :showOther="false" />
</template> </template>
</DataTableObject> </DataTableObject>
<DataTableEnum v-else-if="type === 'enum'" v-model:value="_valueType" placement="topRight" @confirm="(data) => {valueChange(data, 'enum')}"/> <DataTableEnum v-else-if="type === 'enum'" v-model:value="_valueType" placement="bottomRight" @confirm="(data) => {valueChange(data, 'enum')}"/>
<DataTableBoolean v-else-if="type === 'boolean'" v-model:value="_valueType" placement="topRight" @confirm="(data) => {valueChange(data, 'boolean')}" /> <DataTableBoolean v-else-if="type === 'boolean'" v-model:value="_valueType" placement="bottomRight" @confirm="(data) => {valueChange(data, 'boolean')}" />
<DataTableDouble <DataTableDouble
v-else-if="['float', 'double'].includes(type)" v-else-if="['float', 'double'].includes(type)"
:options="unitOptions" :options="unitOptions"
v-model:value="_valueType" v-model:value="_valueType"
placement="topRight" placement="bottomRight"
@confirm="(data) => {valueChange(data, 'float')}" @confirm="(data) => {valueChange(data, 'float')}"
/> />
<DataTableInteger <DataTableInteger
v-else-if="['int', 'long'].includes(type)" v-else-if="['int', 'long'].includes(type)"
:options="unitOptions" :options="unitOptions"
v-model:value="_valueType.unit" v-model:value="_valueType.unit"
placement="topRight" placement="bottomRight"
@confirm="(data) => {valueChange(data, 'int')}" @confirm="(data) => {valueChange(data, 'int')}"
/> />
<DataTableFile v-else-if="type === 'file'" v-model:value="_valueType.fileType" placement="topRight" @confirm="(data) => {valueChange(data, 'file')}"/> <DataTableFile v-else-if="type === 'file'" v-model:value="_valueType.fileType" placement="bottomRight" @confirm="(data) => {valueChange(data, 'file')}"/>
<DataTableDate v-else-if="type === 'date'" v-model:value="_valueType.format" placement="topRight" @confirm="(data) => {valueChange(data, 'date')}"/> <DataTableDate v-else-if="type === 'date'" v-model:value="_valueType.format" placement="bottomRight" @confirm="(data) => {valueChange(data, 'date')}"/>
<DataTableString <DataTableString
v-else-if="['string', 'password'].includes(type)" v-else-if="['string', 'password'].includes(type)"
v-model:value="_valueType.maxLength" v-model:value="_valueType.maxLength"
placement="topRight" placement="bottomRight"
@confirm="(data) => {valueChange(data, 'string')}" @confirm="(data) => {valueChange(data, 'string')}"
/> />
</div> </div>

View File

@ -1,5 +1,5 @@
<template> <template>
<DataTableObject v-model:value="value" :columns="columns" @confirm="confirm"> <DataTableObject v-model:value="value" :columns="columns" @confirm="confirm" placement="bottomRight">
<template #valueType="{ data }"> <template #valueType="{ data }">
<span>{{ TypeStringMap[data.record.valueType?.type] }}</span> <span>{{ TypeStringMap[data.record.valueType?.type] }}</span>
</template> </template>

View File

@ -1,5 +1,5 @@
<template> <template>
<DataTableObject :value="value" :columns="columns" :onAdd="addItem" width="700px" @confirm="confirm"> <DataTableObject :value="value" :columns="columns" :onAdd="addItem" width="700px" @confirm="confirm" placement="bottomRight">
<template #valueType="{ data }"> <template #valueType="{ data }">
<span>{{ TypeStringMap[data.record.valueType?.type] }}</span> <span>{{ TypeStringMap[data.record.valueType?.type] }}</span>
</template> </template>

View File

@ -5,13 +5,14 @@
</div> </div>
<DataTableArray <DataTableArray
v-if="type === 'array'" v-if="type === 'array'"
placement="bottomRight"
v-model:value="data.elementType" v-model:value="data.elementType"
@confirm="valueChange" @confirm="valueChange"
/> />
<DataTableObject <DataTableObject
v-else-if="type === 'object'" v-else-if="type === 'object'"
v-model:value="data.properties" v-model:value="data.properties"
placement="topRight" placement="bottomRight"
:columns="columns" :columns="columns"
@confirm="valueChange" @confirm="valueChange"
:onAdd="addItem" :onAdd="addItem"
@ -23,24 +24,27 @@
<ConfigModal v-model:value="data.record.valueType" :showOther="false"/> <ConfigModal v-model:value="data.record.valueType" :showOther="false"/>
</template> </template>
</DataTableObject> </DataTableObject>
<DataTableEnum v-else-if="type === 'enum'" v-model:value="data" @confirm="valueChange"/> <DataTableEnum v-else-if="type === 'enum'" placement="bottomRight" v-model:value="data" @confirm="valueChange"/>
<DataTableBoolean v-else-if="type === 'boolean'" v-model:value="data" @confirm="valueChange"/> <DataTableBoolean v-else-if="type === 'boolean'" placement="bottomRight" v-model:value="data" @confirm="valueChange"/>
<DataTableDouble <DataTableDouble
v-else-if="['float', 'double'].includes(type)" v-else-if="['float', 'double'].includes(type)"
placement="bottomRight"
:options="unitOptions" :options="unitOptions"
v-model:value="data" v-model:value="data"
@confirm="valueChange" @confirm="valueChange"
/> />
<DataTableInteger <DataTableInteger
placement="bottomRight"
v-else-if="['int', 'long'].includes(type)" v-else-if="['int', 'long'].includes(type)"
:options="unitOptions" :options="unitOptions"
v-model:value="data.unit" v-model:value="data.unit"
@confirm="valueChange" @confirm="valueChange"
/> />
<DataTableFile v-else-if="type === 'file'" v-model:value="data.fileType" @confirm="valueChange"/> <DataTableFile v-else-if="type === 'file'" placement="bottomRight" v-model:value="data.fileType" @confirm="valueChange"/>
<DataTableDate v-else-if="type === 'date'" v-model:value="data.date" @confirm="valueChange"/> <DataTableDate v-else-if="type === 'date'" placement="bottomRight" v-model:value="data.date" @confirm="valueChange"/>
<DataTableString <DataTableString
v-else-if="['string', 'password'].includes(type)" v-else-if="['string', 'password'].includes(type)"
placement="bottomRight"
v-model:value="data.maxLength" v-model:value="data.maxLength"
@confirm="valueChange" @confirm="valueChange"
/> />

View File

@ -1,7 +1,7 @@
<template> <template>
<j-popconfirm-modal <j-popconfirm-modal
body-style="padding-top:4px;width:600px;" body-style="padding-top:4px;width:600px;"
placement="topRight" placement="bottomRight"
:disabled="disabled" :disabled="disabled"
:get-popup-container="(node) => fullRef || node" :get-popup-container="(node) => fullRef || node"
@confirm="confirm" @confirm="confirm"

View File

@ -10,13 +10,13 @@
> >
</j-select> </j-select>
<j-popconfirm-modal <j-popconfirm-modal
v-if="myValue != 'manual'" v-if="myValue != 'manual' && !showReset"
:bodyStyle="{ :bodyStyle="{
width: '450px', width: '450px',
height: myValue === 'rule' ? '300px' : '80px', height: myValue === 'rule' ? '300px' : '80px',
}" }"
:get-popup-container="(node) => fullRef || node" :get-popup-container="(node) => fullRef || node"
placement="topLeft" placement="bottomRight"
@confirm="confirm" @confirm="confirm"
@visibleChange="visibleChange" @visibleChange="visibleChange"
> >
@ -37,6 +37,56 @@
<AIcon type="EditOutlined" /> <AIcon type="EditOutlined" />
</j-button> </j-button>
</j-popconfirm-modal> </j-popconfirm-modal>
<j-dropdown v-if="myValue === 'rule' && target === 'device' && showReset" :getPopupContainer="(triggerNode) => triggerNode.parentNode">
<span style="width: 20px;" @click.prevent>
<AIcon type="MoreOutlined" />
</span>
<template #overlay>
<j-menu>
<j-menu-item>
<j-popconfirm-modal
:bodyStyle="{
width: '450px',
height: myValue === 'rule' ? '300px' : '80px',
}"
:get-popup-container="(node) => fullRef || node"
placement="bottomRight"
@confirm="confirm"
@visibleChange="visibleChange"
>
<template #content>
<j-scrollbar v-if="myValue">
<div style="padding: 0 10px">
<VirtualRule
v-if="visible"
:value="value"
:source="myValue"
:dataSource="dataSource"
ref="virtualRuleRef"
/>
</div>
</j-scrollbar>
</template>
<j-button style="padding: 4px 8px" type="link">
编辑
</j-button>
</j-popconfirm-modal>
</j-menu-item>
<j-menu-divider/>
<j-menu-item>
<div style="display: flex;">
<j-button style="padding: 4px 8px" type="link" @click="resetRules">
重置
</j-button>
<j-tooltip>
<template #title>重置为产品属性规则</template>
<AIcon type="QuestionCircleOutlined" style="margin-top: 10px;"/>
</j-tooltip>
</div>
</j-menu-item>
</j-menu>
</template>
</j-dropdown>
</div> </div>
</template> </template>
@ -45,7 +95,11 @@ import { isNoCommunity } from '@/utils/utils';
import VirtualRule from './VirtualRule/index.vue'; import VirtualRule from './VirtualRule/index.vue';
import { Form } from 'jetlinks-ui-components'; import { Form } from 'jetlinks-ui-components';
import { FULL_CODE } from 'jetlinks-ui-components/es/DataTable' import { FULL_CODE } from 'jetlinks-ui-components/es/DataTable'
import { useInstanceStore } from '@/store/instance';
import { resetRule } from '@/api/device/instance';
import { updata } from '@/api/rule-engine/configuration';
import { onlyMessage } from '@/utils/comm';
const instanceStore = useInstanceStore();
const PropertySource: { label: string; value: string }[] = isNoCommunity const PropertySource: { label: string; value: string }[] = isNoCommunity
? [ ? [
{ {
@ -79,7 +133,7 @@ type Emit = {
}; };
const fullRef = inject(FULL_CODE); const fullRef = inject(FULL_CODE);
const showReset = ref(false);
const props = defineProps({ const props = defineProps({
value: { value: {
type: Object, type: Object,
@ -97,6 +151,10 @@ const props = defineProps({
type: String, type: String,
default: undefined, default: undefined,
}, },
productNoEdit:{
type:Array,
default: []
}
}); });
const emit = defineEmits<Emit>(); const emit = defineEmits<Emit>();
@ -116,7 +174,7 @@ const disabled = computed(() => {
// return true; // return true;
// } // }
return props.noEdit?.length return props.noEdit?.length
? props.noEdit.includes(props.value._sortIndex) ? props.noEdit.includes(props.value.id) && props?.target === 'device'
: false; : false;
}); });
@ -155,7 +213,26 @@ const confirm = async () => {
} }
}); });
}; };
//
// const resetRules = async() =>{
// let res:any = await queryProductVirtualProperty(instanceStore.current?.productId,props.value.id)
// if(res && res.status === 200 && res.result.rule){
// const data:any = {}
// data.virtualRule = res.result.rule
// data.virtualRule.triggerProperties = res.result.triggerProperties
// data.type = type.value
// updateValue({
// source:myValue.value,
// ...data
// })
// }
// }
const resetRules = async() =>{
let res:any = await resetRule(instanceStore.current?.productId,instanceStore.current?.id,props.value?.id)
if(res.status === 200){
onlyMessage('操作成功!')
}
}
const cancel = () => { const cancel = () => {
if (props.value.id && !props.value?.expands?.source) { if (props.value.id && !props.value?.expands?.source) {
myValue.value = 'device'; myValue.value = 'device';
@ -177,6 +254,13 @@ watch(
}, },
{ immediate: true }, { immediate: true },
); );
onMounted(()=>{
if(props.target === 'device'){
props.productNoEdit?.id?.forEach((item:any)=>{
item === props.value?.id ? showReset.value = true : ''
})
}
})
</script> </script>
<style scoped> <style scoped>

View File

@ -257,14 +257,265 @@ const loadData = async () => {
}; };
loadData(); loadData();
// const propertiesSet = new Set(['id','name','expands','valueType']);
// const handleMadeDataNull = (data:any) =>{
// return data?.properties?.some?.((item:any,index:number)=>{
// if(!item?.id){
// onlyMessage(`${index + 1}id`,'error');
// return true
// }
// if(!item?.name){
// onlyMessage(`${index + 1}name`,'error');
// return
// }
// if(!item?.expands?.source){
// onlyMessage(`${index + 1}expands.source`,'error');
// return
// }
// if((item?.expands?.source === 'device' || item?.expands?.source === 'rule') && !item?.expands?.type){
// onlyMessage(`${index + 1}type`,'error');
// return
// }
// }) || false
// }
const requiredCheck = (data:any) =>{
let check:boolean = false;
if(data?.properties && !check){
data.properties.some((item:any,index:number)=>{
if(!item?.id){
onlyMessage(`属性定义第${index + 1}个数组中缺失id属性`,'error');
check = true
return
}
if(!item?.name){
onlyMessage(`属性定义第${index + 1}个数组中缺失name属性`,'error');
check = true
return
}
if(!item?.valueType?.type){
onlyMessage(`标签定义第${index + 1}个数组中缺失valueType.type属性`,'error');
check = true
return
}
if(!item?.expands?.source){
onlyMessage(`属性定义第${index + 1}个数组中缺失expands.source属性`,'error');
check = true
return
}
if((item?.expands?.source === 'device' || item?.expands?.source === 'rule') && !item?.expands?.type){
onlyMessage(`属性定义第${index + 1}个数组中缺失type属性`,'error');
check = true
return
}
})
}
if(data?.functions && !check){
data?.functions.forEach((item:any,index:number)=>{
if(!item?.id){
onlyMessage(`方法定义第${index + 1}个数组中缺失id属性`,'error');
check = true
return
}
if(!item?.name){
onlyMessage(`方法定义第${index + 1}个数组中缺失name属性`,'error');
check = true
return
}
if(!item?.async && item?.async !== false){
onlyMessage(`方法定义第${index + 1}个数组中缺失async属性`,'error');
check = true
return
}
})
}
if(data?.events && !check){
data?.events.forEach((item:any,index:number)=>{
if(!item?.id){
onlyMessage(`事件定义第${index + 1}个数组中缺失id属性`,'error');
check = true
return
}
if(!item?.name){
onlyMessage(`事件定义第${index + 1}个数组中缺失name属性`,'error');
check = true
return
}
if(!item?.async && item?.async !== false){
onlyMessage(`事件定义第${index + 1}个数组中缺失async属性`,'error');
check = true
return
}
if(!item?.valueType?.type){
onlyMessage(`事件定义第${index + 1}个数组中缺失valueType.type属性`,'error');
check = true
return
}
if(!item?.expands?.level){
onlyMessage(`事件定义第${index + 1}个数组中缺失expands.level属性`,'error');
check = true
return
}
if(!check){
if(item?.valueType?.properties){
item?.valueType?.properties.forEach((i:any,number:number)=>{
if(!i?.id){
onlyMessage(`事件定义第${index + 1}个数组中缺失valueType.properties数组第${number+1}项的id属性`,'error');
check = true
return
}
if(!i?.name){
onlyMessage(`事件定义第${index + 1}个数组中缺失valueType.properties数组第${number+1}项的name属性`,'error');
check = true
return
}
if(!i?.valueType?.type){
onlyMessage(`事件定义第${index + 1}个数组中缺失valueType.properties数组第${number+1}项的valueType.type属性`,'error');
check = true
return
}
})
}else{
onlyMessage(`事件定义第${index + 1}个数组中缺失valueType.properties数组`,'error');
check = true
return
}
}
})
}
if(data?.tags && !check){
data?.tags.forEach((item:any,index:number)=>{
if(!item?.id){
onlyMessage(`标签定义第${index + 1}个数组中缺失id属性`,'error');
check = true
return
}
if(!item?.name){
onlyMessage(`标签定义第${index + 1}个数组中缺失name属性`,'error');
check = true
return
}
if(!item?.valueType?.type){
onlyMessage(`标签定义第${index + 1}个数组中缺失valueType.type属性`,'error');
check = true
return
}
if(!item?.expands?.type){
onlyMessage(`标签定义第${index + 1}个数组中缺失expands.type属性`,'error');
check = true
return
}
})
}
return check
}
const aliCheck = (data:any) => {
let check:boolean = false;
if(data?.properties && !check){
data.properties.some((item:any,index:number)=>{
if(!item?.identifier){
onlyMessage(`属性定义第${index + 1}个数组中缺失identifier属性`,'error');
check = true
return
}
if(!item?.name){
onlyMessage(`属性定义第${index + 1}个数组中缺失name属性`,'error');
check = true
return
}
if(!item?.dataType?.type){
onlyMessage(`属性定义第${index + 1}个数组中缺失dataType.type属性`,'error');
check = true
return
}
})
}
if(data?.functions && !check){
data?.functions.forEach((item:any,index:number)=>{
if(!item?.identifier){
onlyMessage(`方法定义第${index + 1}个数组中缺失identifier属性`,'error');
check = true
return
}
if(!item?.name){
onlyMessage(`方法定义第${index + 1}个数组中缺失name属性`,'error');
check = true
return
}
if(!item?.callType){
onlyMessage(`方法定义第${index + 1}个数组中缺失callType属性`,'error');
check = true
return
}
})
}
if(data?.events && !check){
data?.events.forEach((item:any,index:number)=>{
if(!item?.identifier){
onlyMessage(`事件定义第${index + 1}个数组中缺失identifier属性`,'error');
check = true
return
}
if(!item?.name){
onlyMessage(`事件定义第${index + 1}个数组中缺失name属性`,'error');
check = true
return
}
if(!item?.type){
onlyMessage(`事件定义第${index + 1}个数组中缺失type属性`,'error');
check = true
return
}
if(!check){
if(item?.outputData){
item?.outputData?.forEach((i:any,number:number)=>{
if(!i?.identifier){
onlyMessage(`事件定义第${index + 1}个数组中缺失outputData数组第${number+1}项的id属性`,'error');
check = true
return
}
if(!i?.name){
onlyMessage(`事件定义第${index + 1}个数组中缺失outputData数组第${number+1}项的name属性`,'error');
check = true
return
}
if(!i?.dataType?.type){
onlyMessage(`事件定义第${index + 1}个数组中缺失outputData数组第${number+1}项的dataType.type属性`,'error');
check = true
return
}
if(!i?.dataType?.specs){
onlyMessage(`事件定义第${index + 1}个数组中缺失outputData数组第${number+1}项的dataType.specs属性`,'error');
check = true
return
}
})
}else{
onlyMessage(`事件定义第${index + 1}个数组中缺失outputData数组`,'error');
check = true
return
}
}
})
}
return check
}
const beforeUpload: UploadProps['beforeUpload'] = (file) => { const beforeUpload: UploadProps['beforeUpload'] = (file) => {
if(file.type === 'application/json') { if(file.type === 'application/json') {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsText(file); reader.readAsText(file);
reader.onload = (json) => { reader.onload = (json) => {
if(json.target?.result){ if(json.target?.result){
onlyMessage('操作成功!') const data = JSON.parse(json.target?.result);
formModel.import = json.target?.result;
let check = formModel.metadata === 'jetlinks' ? requiredCheck(data) : aliCheck(data)
if(!check){
onlyMessage('操作成功!')
formModel.import = json.target?.result;
}
} else { } else {
onlyMessage('文件内容不能为空', 'error') onlyMessage('文件内容不能为空', 'error')
} }
@ -330,6 +581,7 @@ const handleImport = async () => {
if (data.metadata === 'alink') { if (data.metadata === 'alink') {
try { try {
const _import = JSON.parse(data.import); const _import = JSON.parse(data.import);
loading.value = true; loading.value = true;
const res = await convertMetadata( const res = await convertMetadata(
'from', 'from',
@ -337,7 +589,8 @@ const handleImport = async () => {
_import, _import,
).catch((err) => err); ).catch((err) => err);
if (res.status === 200) { if (res.status === 200) {
const metadata = operateLimits(res.result); // const metadata = operateLimits(res.result); //
const metadata = res.result
let result; let result;
if (props?.type === 'device') { if (props?.type === 'device') {
result = await saveMetadata( result = await saveMetadata(
@ -393,14 +646,15 @@ const handleImport = async () => {
return; return;
} }
const { id } = route.params || {}; const { id } = route.params || {};
const copyOperateLimits = operateLimits( // const copyOperateLimits = operateLimits(
_object as DeviceMetadata, // _object as DeviceMetadata,
); // );
// console.log(copyOperateLimits,_object); //
const params = { const params = {
id, id,
metadata: JSON.stringify(copyOperateLimits), metadata: JSON.stringify(_object),
}; };
const paramsDevice = copyOperateLimits; const paramsDevice = _object;
let resp = undefined; let resp = undefined;
loading.value = true; loading.value = true;
if (props?.type === 'device') { if (props?.type === 'device') {

View File

@ -1,6 +1,6 @@
<template> <template>
<div class='device-detail-metadata' style="position: relative;"> <div class='device-detail-metadata' style="position: relative;">
<!-- <div class="tips">--> <!-- <div class="tips">-->
<!-- <j-tooltip :title="instanceStore.detail?.independentMetadata && type === 'device'--> <!-- <j-tooltip :title="instanceStore.detail?.independentMetadata && type === 'device'-->
<!-- ? '该设备已脱离产品物模型,修改产品物模型对该设备无影响'--> <!-- ? '该设备已脱离产品物模型,修改产品物模型对该设备无影响'-->
<!-- : '设备会默认继承产品的物模型,修改设备物模型后将脱离产品物模型'">--> <!-- : '设备会默认继承产品的物模型,修改设备物模型后将脱离产品物模型'">-->
@ -12,8 +12,8 @@
<!-- : '设备会默认继承产品的物模型,修改设备物模型后将脱离产品物模型'--> <!-- : '设备会默认继承产品的物模型,修改设备物模型后将脱离产品物模型'-->
<!-- }}--> <!-- }}-->
<!-- </div>--> <!-- </div>-->
<!-- </j-tooltip>--> <!-- </j-tooltip> -->
<!-- </div>--> <!-- </div> -->
<j-tabs class="metadataNav" :activeKey="tabActiveKey" destroyInactiveTabPane type="card" @change="tabsChange"> <j-tabs class="metadataNav" :activeKey="tabActiveKey" destroyInactiveTabPane type="card" @change="tabsChange">
<template #rightExtra> <template #rightExtra>
<j-space> <j-space>
@ -30,7 +30,9 @@
<PermissionButton :hasPermission="`${permission}:update`" @click="cat = true" key="tsl">物模型TSL</PermissionButton> <PermissionButton :hasPermission="`${permission}:update`" @click="cat = true" key="tsl">物模型TSL</PermissionButton>
</j-space> </j-space>
</template> </template>
<template #centerExtra>
<span class="desc">设备会默认继承产品的物模型继承的物模型不支持删改</span>
</template>
<j-tab-pane tab="属性定义" key="properties"> <j-tab-pane tab="属性定义" key="properties">
<BaseMetadata :target="type" type="properties" :permission="permission" /> <BaseMetadata :target="type" type="properties" :permission="permission" />
</j-tab-pane> </j-tab-pane>
@ -79,7 +81,6 @@ provide('_metadataType', props.type)
const showReset = computed(() => { const showReset = computed(() => {
if (props.type === 'device' && instanceStore.current.productMetadata) { if (props.type === 'device' && instanceStore.current.productMetadata) {
console.log(instanceStore.current)
const proMetadata = JSON.parse(instanceStore.current.productMetadata || '{}') const proMetadata = JSON.parse(instanceStore.current.productMetadata || '{}')
const _metadata = JSON.parse(instanceStore.current.metadata || '{}') const _metadata = JSON.parse(instanceStore.current.metadata || '{}')
return !isEqual(_metadata, proMetadata) return !isEqual(_metadata, proMetadata)
@ -128,5 +129,12 @@ const tabsChange = (e: string) => {
padding: 0; padding: 0;
} }
} }
.desc{
font-size: 13px;
color: rgba(0,0,0,.8);
display: inline-block;
margin-top: 12px;
margin-left: 5px;
}
} }
</style> </style>

View File

@ -20,9 +20,12 @@ watch(
deviceId.value = newId as string; deviceId.value = newId as string;
_control(newId).then((resp: any) => { _control(newId).then((resp: any) => {
if (resp.status === 200) { if (resp.status === 200) {
const protocol = location.protocol const item = `http://${resp.result?.url}/#/login?token=${resp.result.token}`;
const item = `${protocol}//${resp.result?.url}/#/login?token=${resp.result.token}`; if(window.location.protocol === 'https:' && item){
url.value = item; window.open(item)
}else{
url.value = item;
}
} }
}); });
} }

View File

@ -64,7 +64,7 @@ const filterMenu = (permissions: string[], menus: any[], hasProtocol: boolean) =
return menus.filter((item) => { return menus.filter((item) => {
let isShow = false; let isShow = false;
if (item.showPage && item.showPage.length) { if (item.showPage && item.showPage.length) {
isShow = item.showPage.every((pItem: any) => { isShow = item.showPage.some((pItem: any) => {
return permissions.includes(pItem); return permissions.includes(pItem);
}); });
} }

View File

@ -166,7 +166,9 @@
<div class="card-item-content-text"> <div class="card-item-content-text">
平台对接 平台对接
</div> </div>
<Ellipsis style="width: calc(100% - 20px)">
<div>{{ slotProps.platformConfigName }}</div> <div>{{ slotProps.platformConfigName }}</div>
</Ellipsis>
</j-col> </j-col>
<j-col :span="6"> <j-col :span="6">
<div class="card-item-content-text">类型</div> <div class="card-item-content-text">类型</div>

View File

@ -676,8 +676,10 @@ const saveData = () => {
onlyMessage('操作成功', 'success'); onlyMessage('操作成功', 'success');
if (route.query.save) { if (route.query.save) {
// @ts-ignore // @ts-ignore
window?.onTabSaveSuccess(resp); if((window as any).onTabSaveSuccess){
(window as any).onTabSaveSuccess(resp);
setTimeout(() => window.close(), 300); setTimeout(() => window.close(), 300);
}
} else { } else {
history.back(); history.back();
} }

View File

@ -124,8 +124,10 @@ const onFinish = async (values: any) => {
if (route.query.save) { if (route.query.save) {
// @ts-ignore // @ts-ignore
window?.onTabSaveSuccess(resp.result); if((window as any).onTabSaveSuccess){
setTimeout(() => window.close(), 300); (window as any).onTabSaveSuccess(resp.result);
setTimeout(() => window.close(), 300);
}
} else { } else {
history.back(); history.back();
} }

View File

@ -52,7 +52,7 @@
</div> </div>
</j-spin> </j-spin>
</template> </template>
m
<script lang="ts" setup name="Cpu"> <script lang="ts" setup name="Cpu">
import { dashboard } from '@/api/link/dashboard'; import { dashboard } from '@/api/link/dashboard';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@ -95,6 +95,9 @@ const serverData = reactive({
const pickerTimeChange = () => { const pickerTimeChange = () => {
data.value.type = undefined; data.value.type = undefined;
if(props.isNoCommunity){
getCPUEcharts(data.value)
}
}; };
const echartsOptions = computed(() => { const echartsOptions = computed(() => {

View File

@ -95,6 +95,9 @@ const serverData = reactive({
const pickerTimeChange = () => { const pickerTimeChange = () => {
data.value.type = undefined; data.value.type = undefined;
if(props.isNoCommunity){
getJVMEcharts(data.value)
}
}; };
const getJVMEcharts = async (val: any) => { const getJVMEcharts = async (val: any) => {

View File

@ -215,6 +215,7 @@ const columns = [
] ]
const handleAdd = () => { const handleAdd = () => {
editData.value = {}
visible.value = true visible.value = true
} }

View File

@ -560,8 +560,10 @@ const handleSubmit = () => {
onlyMessage('保存成功'); onlyMessage('保存成功');
if (route.query?.notifyType) { if (route.query?.notifyType) {
// @ts-ignore // @ts-ignore
window?.onTabSaveSuccess(res.result); if((window as any).onTabSaveSuccess){
setTimeout(() => window.close(), 300); (window as any).onTabSaveSuccess(res.result);
setTimeout(() => window.close(), 300);
}
} else { } else {
router.back(); router.back();
} }
@ -587,8 +589,10 @@ const handleSubmit = () => {
onlyMessage('保存成功'); onlyMessage('保存成功');
if (route.query?.notifyType) { if (route.query?.notifyType) {
// @ts-ignore // @ts-ignore
window?.onTabSaveSuccess(res.result); if((window as any).onTabSaveSuccess){
setTimeout(() => window.close(), 300); (window as any).onTabSaveSuccess(res.result);
setTimeout(() => window.close(), 300);
}
} else { } else {
router.back(); router.back();
} }

View File

@ -1261,8 +1261,10 @@ const handleSubmit = () => {
onlyMessage('保存成功'); onlyMessage('保存成功');
if (route.query?.notifyType) { if (route.query?.notifyType) {
// @ts-ignore // @ts-ignore
window?.onTabSaveSuccess(res.result); if((window as any).onTabSaveSuccess){
setTimeout(() => window.close(), 300); (window as any).onTabSaveSuccess(res.result);
setTimeout(() => window.close(), 300);
}
} else { } else {
router.back(); router.back();
} }

View File

@ -74,10 +74,11 @@
<script setup lang='ts' name='Oauth'> <script setup lang='ts' name='Oauth'>
import { TOKEN_KEY } from '@/utils/variable' import { TOKEN_KEY } from '@/utils/variable'
import { config, code, getOAuth2, initApplication, authLogin, settingDetail } from '@/api/login' import { config, code, getOAuth2, initApplication, authLogin, settingDetail,authLoginConfig } from '@/api/login'
import { getMe_api } from '@/api/home' import { getMe_api } from '@/api/home'
import { getImage, getToken } from '@/utils/comm' import { getImage, getToken } from '@/utils/comm'
import Config from '../../../config/config' import Config from '../../../config/config'
import {encrypt} from '@/utils/encrypt'
const spinning = ref(true) const spinning = ref(true)
const isLogin = ref(false) const isLogin = ref(false)
@ -193,11 +194,27 @@ const getQueryVariable = (): Map<string, string> => {
return maps; return maps;
} }
const RsaConfig = reactive<any>({
enabled:false, //
publicKey:'', //rsa,使
id:'' //ID
})
const doLogin = () => { const doLogin = () => {
formRef.value.validate().then( async data => { formRef.value.validate().then( async data => {
const res = await authLogin({ const resq:any = await authLoginConfig()
if(resq.status === 200){
if(resq.result?.encrypt){
RsaConfig.enabled = resq.result?.encrypt.enabled
RsaConfig.publicKey = resq.result?.encrypt.publicKey
RsaConfig.id = resq.result?.encrypt.id
}
}
const res:any = await authLogin({
verifyKey: captcha.key, verifyKey: captcha.key,
...formModel ...formModel,
password:RsaConfig.enabled?encrypt(formModel.password,RsaConfig.publicKey):formModel.password,
encryptId:RsaConfig.enabled?RsaConfig.id:undefined
}) })
if (res.success) { if (res.success) {
const token = res.result.token const token = res.result.token

View File

@ -10,10 +10,22 @@
okText="确定" okText="确定"
> >
<j-form layout="vertical" :model="inputData" ref="formRef"> <j-form layout="vertical" :model="inputData" ref="formRef">
<j-form-item label="状态">
<j-switch
checked-children="启用"
un-checked-children="启用"
v-model:checked="inputData.status"
></j-switch>
</j-form-item>
<j-form-item <j-form-item
v-if="inputData.status"
label="kafka地址" label="kafka地址"
name="address" name="address"
:rules="[ :rules="[
{
required: true,
message: '请输入topic',
},
{ {
max: 64, max: 64,
message: '最多输入64个字符', message: '最多输入64个字符',
@ -26,9 +38,14 @@
></j-input> ></j-input>
</j-form-item> </j-form-item>
<j-form-item <j-form-item
v-if="inputData.status"
label="topic" label="topic"
name="topic" name="topic"
:rules="[ :rules="[
{
required: true,
message: '请输入topic',
},
{ {
max: 64, max: 64,
message: '最多输入64个字符', message: '最多输入64个字符',
@ -37,13 +54,6 @@
> >
<j-input v-model:value="inputData.topic"></j-input> <j-input v-model:value="inputData.topic"></j-input>
</j-form-item> </j-form-item>
<j-form-item label="状态">
<j-switch
checked-children="启用"
un-checked-children="启用"
v-model:checked="inputData.status"
></j-switch>
</j-form-item>
</j-form> </j-form>
</j-modal> </j-modal>
</template> </template>

View File

@ -25,7 +25,7 @@
props.data?.alarmConfigName props.data?.alarmConfigName
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="告警时间" :span="1">{{ <j-descriptions-item label="告警时间" :span="1">{{
dayjs(data?.alarmTime).format('YYYY-MM-DD HH:mm:ss') dayjs(props.data?.alarmTime).format('YYYY-MM-DD HH:mm:ss')
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="告警级别" :span="1"> <j-descriptions-item label="告警级别" :span="1">
<j-tooltip <j-tooltip

View File

@ -133,6 +133,7 @@ import {
getDeviceList, getDeviceList,
getOrgList, getOrgList,
query, query,
getAlarmProduct
} from '@/api/rule-engine/log'; } from '@/api/rule-engine/log';
import { queryLevel } from '@/api/rule-engine/config'; import { queryLevel } from '@/api/rule-engine/config';
import Search from '@/components/Search'; import Search from '@/components/Search';
@ -285,7 +286,51 @@ const newColumns = computed(() => {
otherColumns.title = '场景名称' otherColumns.title = '场景名称'
break; break;
} }
if(props.type === 'device'){
const productColumns = {
title: '产品名称',
dataIndex: 'product_id',
key: 'product_id',
search: {
type: 'select',
options: async () => {
const termType = [
{
column:"id$alarm-record",
value:[
{
column: "targetType",
termType: "eq",
value: "device",
}
]
}
]
const resp: any = await getAlarmProduct({
sorts: [{ name: 'alarmTime', order: 'desc' }],
terms: termType
});
const listMap: Map<string, any> = new Map()
if (resp.status === 200) {
resp.result.data.forEach(item => {
if (item.productId) {
listMap.set(item.productId, {
label: item.productName,
value: item.productId,
})
}
})
return [...listMap.values()]
}
return [];
},
},
}
return [otherColumns,productColumns,...columns]
}
return ['all', 'detail'].includes(props.type) ? columns : [ return ['all', 'detail'].includes(props.type) ? columns : [
otherColumns, otherColumns,
...columns, ...columns,
@ -297,9 +342,9 @@ let params: any = ref({
terms: [], terms: [],
}); });
const handleSearch = async (params: any) => { const handleSearch = async (params: any) => {
const resp = await query(params); const resp:any = await query(params);
if (resp.status === 200) { if (resp.status === 200) {
const res = await getOrgList(); const res:any = await getOrgList();
if (res.status === 200) { if (res.status === 200) {
resp.result.data.map((item: any) => { resp.result.data.map((item: any) => {
if (item.targetType === 'org') { if (item.targetType === 'org') {
@ -318,7 +363,7 @@ const handleSearch = async (params: any) => {
} }
} }
}; };
watchEffect(() => { onMounted(() => {
if (props.type !== 'all' && !props.id) { if (props.type !== 'all' && !props.id) {
params.value.terms = [ params.value.terms = [
{ {
@ -354,6 +399,14 @@ const search = (data: any) => {
type: 'and', type: 'and',
}); });
} }
if(props.type === 'device' && data?.terms[0]?.terms[0]?.column === 'product_id'){
params.value.terms = [{
column:"targetId$dev-instance",
value:[
data?.terms[0]?.terms[0]
]
}]
}
if (props.id) { if (props.id) {
params.value.terms.push({ params.value.terms.push({
termType: 'eq', termType: 'eq',

View File

@ -16,6 +16,7 @@
</j-radio-button> </j-radio-button>
</j-radio-group> </j-radio-group>
<j-range-picker <j-range-picker
:show-time="{format:'HH:mm:ss'}"
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
valueFormat="YYYY-MM-DD HH:mm:ss" valueFormat="YYYY-MM-DD HH:mm:ss"
style="margin-left: 12px" style="margin-left: 12px"

View File

@ -357,7 +357,6 @@ getCurrentAlarm();
const initQueryTime = (data: any) => { const initQueryTime = (data: any) => {
queryCodition.startTime = data.start; queryCodition.startTime = data.start;
queryCodition.endTime = data.end; queryCodition.endTime = data.end;
console.log(queryCodition);
selectChange(); selectChange();
}; };
const selectChange = () => { const selectChange = () => {
@ -439,10 +438,14 @@ const selectChange = () => {
res.result res.result
.filter((item: any) => item.group === 'alarmTrend') .filter((item: any) => item.group === 'alarmTrend')
.forEach((item: any) => { .forEach((item: any) => {
if(time === '1d'){
item.data.timeString = item.data.timeString.split(' ')[0]
}
xData.push(item.data.timeString); xData.push(item.data.timeString);
sData.push(item.data.value); sData.push(item.data.value);
}); });
const maxY = sData.sort((a,b)=>{ const data:any = JSON.parse(JSON.stringify(sData))
const maxY = data.sort((a,b)=>{
return b-a return b-a
})[0] })[0]
alarmStatisticsOption.value = { alarmStatisticsOption.value = {

View File

@ -47,7 +47,7 @@
<div class="card-item-content-text"> <div class="card-item-content-text">
设备类型 设备类型
</div> </div>
<div>直连设备</div> <div>{{ slotProps.deviceType.text}}</div>
</j-col> </j-col>
</j-row> </j-row>
</template> </template>

View File

@ -243,7 +243,7 @@ const deleteScene = async (id: string) => {
const deleteModal = (id: string) => { const deleteModal = (id: string) => {
Modal.confirm({ Modal.confirm({
title: '场景已绑定告警,确定删除?', title: '场景已绑定告警,确定删除?',
onOk: async () => { onOk: async () => {
await deleteScene(id) await deleteScene(id)
} }

View File

@ -1,41 +1,17 @@
<template> <template>
<j-modal <j-modal visible title="集成菜单" width="600px" @ok="handleOk" @cancel="cancel" class="edit-dialog-container"
visible :confirmLoading="loading">
title="集成菜单" <j-select v-model:value="form.checkedSystem" @change="(value) => value && getTree(value) " style="width: 200px"
width="600px" placeholder="请选择集成系统">
@ok="handleOk" <j-select-option v-for="item in form.systemList" :value="item.value" :key="item.value">{{ item.label
@cancel="cancel" }}</j-select-option>
class="edit-dialog-container"
:confirmLoading="loading"
>
<j-select
v-model:value="form.checkedSystem"
@change="(value) => value && getTree(value)"
style="width: 200px"
placeholder="请选择集成系统"
>
<j-select-option
v-for="item in form.systemList"
:value="item.value"
:key="item.value"
>{{ item.label }}</j-select-option
>
</j-select> </j-select>
<p style="margin: 20px 0 0 0" v-show="form.menuTree.length > 0"> <p style="margin: 20px 0 0 0" v-show="form.menuTree.length > 0">
当前集成菜单 当前集成菜单
</p> </p>
<j-tree <j-tree v-model:checkedKeys="form.checkedMenu" v-model:expandedKeys="form.expandedKeys" checkable
v-model:checkedKeys="form.checkedMenu" :tree-data="form.menuTree" :fieldNames="{ key: 'code', title: 'name' }" @check="treeCheck" :height="300">
v-model:expandedKeys="form.expandedKeys"
checkable
:tree-data="form.menuTree"
:fieldNames="{ key: 'code', title: 'name' }"
@check="treeCheck"
:height="300"
:showLine="{ showLeafIcon: false }"
:show-icon="true"
>
<template #title="{ name }"> <template #title="{ name }">
<span>{{ name }}</span> <span>{{ name }}</span>
</template> </template>
@ -68,10 +44,12 @@ const props = defineProps<{
// //
const loading = ref(false); const loading = ref(false);
const handleOk = async () => { const handleOk = async () => {
const items = filterTree(form.menuTree, [ const menuTree = JSON.parse(JSON.stringify(form.menuTree));
const items = filterTree(menuTree, [
...form.checkedMenu, ...form.checkedMenu,
...form.half, // ...form.half,
]); ]);
console.log(items);
if (form.checkedSystem) { if (form.checkedSystem) {
if (items && items.length !== 0) { if (items && items.length !== 0) {
loading.value = true; loading.value = true;
@ -130,6 +108,16 @@ function getTree(params: string) {
form.expandedKeys = resp.result.map((item: any) => item?.code); form.expandedKeys = resp.result.map((item: any) => item?.code);
}); });
} }
const getCheckMenu = (data: any, keys: any) => {
data.forEach((item: any) => {
if (item.children) {
getCheckMenu(item.children, keys)
} else {
keys.push(item.code)
}
})
}
/** /**
* 获取当前用户可访问菜单 * 获取当前用户可访问菜单
*/ */
@ -145,7 +133,9 @@ function getMenus(id: string) {
getMenuTree_api(params).then((resp: any) => { getMenuTree_api(params).then((resp: any) => {
if (resp.status === 200) { if (resp.status === 200) {
// form.menuTree = resp.result; // form.menuTree = resp.result;
const keys = resp.result.map((item: any) => item?.code) as string[]; // const keys = resp.result.map((item: any) => item?.code) as string[];
let keys: any = [];
getCheckMenu(resp.result, keys)
// form.expandedKeys = keys; // form.expandedKeys = keys;
form.checkedMenu = keys; form.checkedMenu = keys;
} }
@ -172,10 +162,10 @@ function getSystemList(id: string) {
watch(() => props.data, (newVal: any) => { watch(() => props.data, (newVal: any) => {
form.checkedSystem = newVal?.page.configuration?.checkedSystem form.checkedSystem = newVal?.page.configuration?.checkedSystem
if(form.checkedSystem){ if (form.checkedSystem) {
getTree(form.checkedSystem) getTree(form.checkedSystem)
} }
if(newVal?.id) { if (newVal?.id) {
getSystemList(newVal?.id); getSystemList(newVal?.id);
getMenus(newVal?.id); getMenus(newVal?.id);
} }
@ -190,17 +180,23 @@ function treeCheck(checkedKeys: any, e: CheckInfo) {
} }
//- //-
function filterTree(nodes: any[], list: any[]) { function filterTree(nodes: any[], list: any[]) {
if (!nodes?.length) { if (!nodes) {
return nodes; return [];
} }
return nodes.filter((it) => { return nodes.filter((it) => {
// //
if (list.indexOf(it.code) <= -1) { if (list.includes(it.code)) {
return false; return true
} else if (it.children) {
it.children = filterTree(it.children, list);
return it.children.length
} }
// if (list.indexOf(it.code) <= -1) {
// return false;
// }
// //
it.children = filterTree(it.children, list); // it.children = filterTree(it.children, list);
return true; // return true;
}); });
} }
</script> </script>

View File

@ -6,20 +6,22 @@
只能分配有共享权限的资产数据 只能分配有共享权限的资产数据
</h5> </h5>
<div class="row"> <div style="display: flex; margin-left: 24px;">
<span style="margin-right: 8px">批量配置</span> <div class="row">
<j-switch v-model:checked="bulkBool" checked-children="" un-checked-children="" style="width: 56px" /> <span style="margin-right: 8px">批量配置</span>
</div> <j-switch v-model:checked="bulkBool" checked-children="" un-checked-children="" style="width: 56px" />
<div v-show="bulkBool"> </div>
<j-checkbox-group v-model:value="bulkList" :options="options" /> <div v-show="bulkBool" style="margin-left: 30px;">
<j-checkbox-group v-model:value="bulkList" :options="options" />
</div>
</div> </div>
<pro-search <!-- <pro-search
type="simple" type="simple"
:columns="searchColumns" :columns="searchColumns"
target="category-bind-modal" target="category-bind-modal"
@search="search" @search="search"
/> /> -->
<j-pro-table <j-pro-table
ref="tableRef" ref="tableRef"
:request="table.requestFun" :request="table.requestFun"
@ -32,7 +34,17 @@
onSelectAll: selectAll onSelectAll: selectAll
}" }"
:columns="columns" :columns="columns"
style="max-height: 500px; overflow:auto"
> >
<template #headerTitle>
<pro-search
type="simple"
:columns="searchColumns"
target="category-bind-modal"
@search="search"
style="width: 75%;"
/>
</template>
<template #card="slotProps"> <template #card="slotProps">
<CardBox :value="slotProps" :actions="[{ key: 1 }]" v-bind="slotProps" :active="table._selectedRowKeys.value.includes(slotProps.id) <CardBox :value="slotProps" :actions="[{ key: 1 }]" v-bind="slotProps" :active="table._selectedRowKeys.value.includes(slotProps.id)
" @click="table.onSelectChange" :status="slotProps.state?.value" " @click="table.onSelectChange" :status="slotProps.state?.value"
@ -312,8 +324,8 @@ const table: any = {
(perResp: any) => { (perResp: any) => {
data.forEach((item) => { data.forEach((item) => {
item.permissionList = perResp.result item.permissionList = perResp.result
.find((f: any) => f.assetId === item.id) .find((f: any) => f?.assetId === item.id)
.permissionInfoList?.map((m: any) => ({ ?.permissionInfoList?.map((m: any) => ({
label: m.name, label: m.name,
value: m.id, value: m.id,
disabled: true, disabled: true,
@ -321,13 +333,13 @@ const table: any = {
item.selectPermissions = ['read']; item.selectPermissions = ['read'];
// //
item.permissionList = item.permissionList item.permissionList = item.permissionList
.map((m: any) => { ?.map((m: any) => {
return { return {
...m, ...m,
idx: idxMap[m.value], idx: idxMap[m.value],
}; };
}) })
.sort((a: any, b: any) => a.idx - b.idx); ?.sort((a: any, b: any) => a.idx - b.idx);
// //
if (props.assetType === 'product') { if (props.assetType === 'product') {
item.state = { item.state = {
@ -490,6 +502,7 @@ const search = (query: any) => {
h5 { h5 {
padding: 12px; padding: 12px;
padding-left: 24px;
background-color: #f6f6f6; background-color: #f6f6f6;
font-size: 14px; font-size: 14px;
} }
@ -498,4 +511,7 @@ const search = (query: any) => {
margin-bottom: 12px; margin-bottom: 12px;
} }
} }
:deep(.jtable-body-header-left){
width: 80%;
}
</style> </style>

View File

@ -176,6 +176,9 @@ const form = reactive({
submit: () => { submit: () => {
const api = form.data.id ? updateDepartment_api : addDepartment_api; const api = form.data.id ? updateDepartment_api : addDepartment_api;
form.data.parentId = form.data.parentId ? form.data.parentId : ''; form.data.parentId = form.data.parentId ? form.data.parentId : '';
if(form.data.id && form.data?.children){
delete(form.data.children)
}
return api(form.data); return api(form.data);
}, },
}); });

View File

@ -130,12 +130,12 @@ function getTree(cb?: Function) {
treeMap.clear() treeMap.clear()
getTreeData_api(params) getTreeData_api(params)
.then((resp: any) => { .then((resp: any) => {
selectedKeys.value = [resp.result[0]?.id];
sourceTree.value = resp.result.sort((a: any, b: any) => sourceTree.value = resp.result.sort((a: any, b: any) =>
a.sortIndex === b.sortIndex a.sortIndex === b.sortIndex
? b.createTime - a.createTime ? b.createTime - a.createTime
: a.sortIndex - b.sortIndex, : a.sortIndex - b.sortIndex,
); // ); //
selectedKeys.value = [resp.result[0]?.id];
handleTreeMap(resp.result); // map handleTreeMap(resp.result); // map
treeData.value = resp.result; // treeData.value = resp.result; //
cb && cb(); cb && cb();
@ -155,6 +155,7 @@ const search = debounce(() => {
treeArray.set(item.id, item); treeArray.set(item.id, item);
} }
}); });
expandedKeys.value = []
dig(searchTree); dig(searchTree);
treeData.value = ArrayToTree(cloneDeep([...treeArray.values()])); treeData.value = ArrayToTree(cloneDeep([...treeArray.values()]));
} else { } else {
@ -170,6 +171,10 @@ const search = debounce(() => {
const _item = treeMap.get(item); const _item = treeMap.get(item);
pIds.push(_item.parentId); pIds.push(_item.parentId);
treeArray.set(item, _item); treeArray.set(item, _item);
expandedKeys.value.push(_item.id)
if(pIds.length > 0){
dig(pIds)
}
} }
}); });
} }
@ -194,8 +199,10 @@ function delDepartment(id: string) {
} }
function refresh(id: string) { function refresh(id: string) {
// @ts-ignore // @ts-ignore
window?.onTabSaveSuccess && window.onTabSaveSuccess(id); if(window?.onTabSaveSuccess){
setTimeout(() => window.close(), 300); window.onTabSaveSuccess(id);
setTimeout(() => window.close(), 300);
}
getTree(); getTree();
} }
@ -268,7 +275,7 @@ init();
.tree { .tree {
overflow-y: auto; overflow-y: auto;
overflow-x: auto; overflow-x: auto;
flex: 1 1 auto;
.department-tree-item-content { .department-tree-item-content {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -14,6 +14,7 @@
:columns="columns" :columns="columns"
target="category" target="category"
@search="(params:any)=>queryParams = {...params}" @search="(params:any)=>queryParams = {...params}"
style="margin-bottom: 0;"
/> />
<div class="table"> <div class="table">
<j-pro-table <j-pro-table
@ -31,6 +32,7 @@
:defaultParams="{ :defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }], sorts: [{ name: 'createTime', order: 'desc' }],
}" }"
style="max-height: 510px; overflow: auto; padding-top: 0;"
/> />
</div> </div>
</j-modal> </j-modal>

View File

@ -253,6 +253,7 @@
<ChooseIconDialog <ChooseIconDialog
v-if="dialogVisible" v-if="dialogVisible"
v-model:visible="dialogVisible" v-model:visible="dialogVisible"
:icon="form.data.icon"
@confirm="(typeStr:string)=>choseIcon(typeStr)" @confirm="(typeStr:string)=>choseIcon(typeStr)"
/> />
</div> </div>

View File

@ -237,7 +237,6 @@ const filterMenus = (menus: any[]) => {
item.children = filterMenus(item.children); item.children = filterMenus(item.children);
} }
if (!filterProtocolList.length && item.code == 'link/DataCollect') { if (!filterProtocolList.length && item.code == 'link/DataCollect') {
debugger
return false; return false;
} }
return item return item

View File

@ -24,6 +24,10 @@ import iconKeys from './fields';
const emits = defineEmits(['confirm', 'update:visible']); const emits = defineEmits(['confirm', 'update:visible']);
const props = defineProps<{ const props = defineProps<{
visible: boolean; visible: boolean;
icon:{
type:string,
default:''
}
}>(); }>();
const confirm = () => { const confirm = () => {
@ -31,7 +35,13 @@ const confirm = () => {
emits('update:visible', false); emits('update:visible', false);
}; };
const selected = ref<string>(''); const selected = ref<string>('');
onMounted(()=>{
console.log(props)
props?.icon ? selected.value = props.icon : ''
})
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -50,11 +50,11 @@
<AIcon type="EditOutlined" /> <AIcon type="EditOutlined" />
</j-button> </j-button>
</j-tooltip> </j-tooltip>
<PermissionButton <PermissionButton
type="link" type="link"
:hasPermission="`${permission}:add`" :hasPermission="`${permission}:add`"
:tooltip="{ title: '新增子菜单' }" :tooltip="{ title: '新增子菜单' }"
:disabled="slotProps.level >= 3"
@click="table.addChildren(slotProps)" @click="table.addChildren(slotProps)"
> >
<AIcon type="PlusCircleOutlined" /> <AIcon type="PlusCircleOutlined" />

View File

@ -23,6 +23,7 @@
hasPermission="system/Platforms/Setting:update" hasPermission="system/Platforms/Setting:update"
@click="save" @click="save"
v-if="props.mode !== 'home'" v-if="props.mode !== 'home'"
style="margin-left: 20px;"
> >
保存 保存
</PermissionButton> </PermissionButton>

View File

@ -157,7 +157,7 @@ const filterPath = (path: object, filterArr: string[]) => {
.ant-tree-list { .ant-tree-list {
.ant-tree-list-holder-inner { .ant-tree-list-holder-inner {
.ant-tree-switcher-noop { .ant-tree-switcher-noop {
display: none !important; // display: none !important;
} }
} }
} }

View File

@ -3,11 +3,11 @@
<div class="top"> <div class="top">
<slot name="top" /> <slot name="top" />
</div> </div>
<j-row :gutter="24" class="content"> <j-row class="content" :style="{padding:'24px'}" >
<j-col <j-col
:span="24" :span="24"
v-if="props.showTitle" v-if="props.showTitle"
style="font-size: 16px; margin-bottom: 48px" style="font-size: 16px;margin-bottom: 48px;"
> >
API文档 API文档
</j-col> </j-col>
@ -180,7 +180,6 @@ watch(
.api-page-container { .api-page-container {
.content { .content {
background-color: #fff; background-color: #fff;
padding: 24px;
margin: 0 !important; margin: 0 !important;
.tree-content { .tree-content {
padding-bottom: 30px; padding-bottom: 30px;

View File

@ -64,8 +64,10 @@ const confirm = () => {
if (route.query.save) { if (route.query.save) {
// @ts-ignore // @ts-ignore
window?.onTabSaveSuccess(resp.result.id); if((window as any).onTabSaveSuccess){
setTimeout(() => window.close(), 300); (window as any).onTabSaveSuccess(resp.result.id);
setTimeout(() => window.close(), 300);
}
} else jumpPage(`system/Role/Detail`, { id: resp.result.id }); } else jumpPage(`system/Role/Detail`, { id: resp.result.id });
} }
}) })