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

This commit is contained in:
JiangQiming 2023-03-21 18:16:16 +08:00
commit 8583d968c3
14 changed files with 734 additions and 232 deletions

View File

@ -0,0 +1,106 @@
<template>
<div>
<j-space v-if="_item.key && visible">
<PermissionButton
:type="'primary'"
:ghost="true"
:hasPermission="_item.permission ? _item.permission : true"
v-bind="{ ..._item, ..._item.selected }"
>
<template #icon><AIcon :type="_item.icon" /></template>
{{ _item.text }}
</PermissionButton>
<j-button type="link" @click="reload"
><AIcon type="RedoOutlined" />重选</j-button
>
</j-space>
<j-dropdown :overlayStyle="{ zIndex: 1000 }" v-else>
<j-button>批量操作 <AIcon type="DownOutlined" /></j-button>
<template #overlay>
<j-menu @click="handleMenuClick">
<j-menu-item v-for="item in actions" :key="item.key">
<PermissionButton
:hasPermission="
item.permission ? item.permission : true
"
v-bind="item"
:popConfirm="
item.popConfirm
? {
...item.popConfirm,
onCancel: onPopCancel,
onConfirm: (e) =>
onPopConfirm(
e,
item?.popConfirm?.onConfirm,
),
}
: undefined
"
>
<template #icon
><AIcon :type="item.icon"
/></template>
{{ item.text }}
</PermissionButton>
</j-menu-item>
</j-menu>
</template>
</j-dropdown>
</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue';
import { BatchActionsType } from './types';
const props = defineProps({
actions: {
type: Array as PropType<BatchActionsType[]>,
default: () => [],
},
isCheck: {
type: Boolean,
default: false,
},
});
const emits = defineEmits(['update:isCheck', 'change']);
const visible = ref<boolean>(false);
const _item = ref<Partial<BatchActionsType>>({});
const handleMenuClick = (e: any) => {
const val = props.actions.find((item) => item.key === e.key);
if(!(val?.popConfirm || val?.onClick)){
emits('update:isCheck', true);
emits('change', true);
}
if (val?.popConfirm) {
visible.value = false;
} else {
visible.value = true;
}
_item.value = (val || {}) as any;
};
const reload = () => {
_item.value = {};
emits('update:isCheck', false);
emits('change', false);
};
const onPopConfirm = (e: any, fun: any) => {
if (fun) {
fun(e);
onPopCancel();
}
};
const onPopCancel = () => {
visible.value = false;
};
</script>
<style lang="less" scoped>
</style>

16
src/components/BatchDropdown/types.d.ts vendored Normal file
View File

@ -0,0 +1,16 @@
import type { ButtonProps } from "ant-design-vue/es/button";
import type { PopconfirmProps } from "ant-design-vue/es/popconfirm";
export interface BatchActionsType extends ButtonProps {
key: string;
text?: string;
permission?: string;
onClick?: (data: any) => void;
style?: CSSProperties;
popConfirm?: PopconfirmProps;
icon?: string;
selected?: {// 需要选择表格数据,后触发的事件
popConfirm?: PopconfirmProps;
onClick?: (data: any) => void
}
}

View File

@ -55,6 +55,7 @@ import { CSSProperties, PropType } from 'vue'
import { TooltipProps, PopconfirmProps } from 'ant-design-vue/es'
import { buttonProps } from 'ant-design-vue/es/button/button'
import { usePermissionStore } from '@/store/permission';
import { omit } from 'lodash-es';
// interface PermissionButtonEmits {
// (e: 'click', data: MouseEvent): void;
@ -88,7 +89,7 @@ const props = defineProps({
style: {
type: Object as PropType<CSSProperties>
},
...buttonProps()
...omit(buttonProps(), 'icon')
})
// const { tooltip, popConfirm, hasPermission, noButton, ..._buttonProps } = props;

View File

@ -10,10 +10,14 @@
:columns="columns"
:request="query"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
:rowSelection="{
selectedRowKeys: _selectedRowKeys,
onChange: onSelectChange,
}"
:rowSelection="
isCheck
? {
selectedRowKeys: _selectedRowKeys,
onChange: onSelectChange,
}
: false
"
:params="params"
>
<template #headerTitle>
@ -26,7 +30,12 @@
<template #icon><AIcon type="PlusOutlined" /></template>
新增
</PermissionButton>
<j-dropdown>
<BatchDropdown
v-model:isCheck="isCheck"
:actions="batchActions"
@change="onCheckChange"
/>
<!-- <j-dropdown>
<j-button
>批量操作 <AIcon type="DownOutlined"
/></j-button>
@ -131,7 +140,7 @@
</j-menu-item>
</j-menu>
</template>
</j-dropdown>
</j-dropdown> -->
</j-space>
</template>
<template #card="slotProps">
@ -155,10 +164,7 @@
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)">
<span
style="font-size: 16px; font-weight: 600"
@click.stop="handleView(slotProps.id)"
>
<span style="font-size: 16px; font-weight: 600">
{{ slotProps.name }}
</span>
</Ellipsis>
@ -233,7 +239,11 @@
type="link"
style="padding: 0 5px"
:danger="i.key === 'delete'"
:hasPermission="i.key === 'view' ? true : 'device/Instance:' + i.key"
:hasPermission="
i.key === 'view'
? true
: 'device/Instance:' + i.key
"
>
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
@ -296,6 +306,8 @@ import { useMenuStore } from '@/store/menu';
import type { ActionsType } from './typings';
import dayjs from 'dayjs';
import BadgeStatus from '@/components/BadgeStatus/index.vue';
import BatchDropdown from '@/components/BatchDropdown/index.vue';
import { BatchActionsType } from '@/components/BatchDropdown/types';
const instanceRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({});
@ -307,6 +319,7 @@ const current = ref<Record<string, any>>({});
const operationVisible = ref<boolean>(false);
const api = ref<string>('');
const type = ref<string>('');
const isCheck = ref<boolean>(false);
const menuStory = useMenuStore();
@ -657,14 +670,22 @@ const onSelectChange = (keys: string[]) => {
};
const handleClick = (dt: any) => {
if (_selectedRowKeys.value.includes(dt.id)) {
const _index = _selectedRowKeys.value.findIndex((i) => i === dt.id);
_selectedRowKeys.value.splice(_index, 1);
if (isCheck.value) {
if (_selectedRowKeys.value.includes(dt.id)) {
const _index = _selectedRowKeys.value.findIndex((i) => i === dt.id);
_selectedRowKeys.value.splice(_index, 1);
} else {
_selectedRowKeys.value = [..._selectedRowKeys.value, dt.id];
}
} else {
_selectedRowKeys.value = [..._selectedRowKeys.value, dt.id];
handleView(dt.id);
}
};
const onCheckChange = () => {
_selectedRowKeys.value = [];
};
const activeAllDevice = () => {
type.value = 'active';
const activeAPI = `${BASE_API_PATH}/device-instance/deploy?:X_Access_Token=${LocalStore.get(
@ -684,6 +705,10 @@ const syncDeviceStatus = () => {
};
const delSelectedDevice = async () => {
if(!_selectedRowKeys.value.length){
message.error('请选择设备')
return
}
const resp = await batchDeleteDevice(_selectedRowKeys.value);
if (resp.status === 200) {
message.success('操作成功!');
@ -692,16 +717,24 @@ const delSelectedDevice = async () => {
}
};
const activeSelectedDevice = async () => {
const resp = await batchDeployDevice(_selectedRowKeys.value);
if (resp.status === 200) {
message.success('操作成功!');
_selectedRowKeys.value = [];
instanceRef.value?.reload();
}
};
// const activeSelectedDevice = async () => {
// if(!_selectedRowKeys.value.length){
// message.error('')
// return
// }
// const resp = await batchDeployDevice(_selectedRowKeys.value);
// if (resp.status === 200) {
// message.success('');
// _selectedRowKeys.value = [];
// instanceRef.value?.reload();
// }
// };
const disabledSelectedDevice = async () => {
if(!_selectedRowKeys.value.length){
message.error('请选择设备')
return
}
const resp = await batchUndeployDevice(_selectedRowKeys.value);
if (resp.status === 200) {
message.success('操作成功!');
@ -710,6 +743,87 @@ const disabledSelectedDevice = async () => {
}
};
const batchActions: BatchActionsType[] = [
{
key: 'export',
text: '批量导出设备',
permission: 'device/Instance:export',
icon: 'ExportOutlined',
onClick: () => {
exportVisible.value = true;
},
},
{
key: 'import',
text: '批量导入设备',
permission: 'device/Instance:import',
icon: 'ImportOutlined',
onClick: () => {
importVisible.value = true;
},
},
{
key: 'activeAll',
text: '启用全部设备',
ghost: true,
type: 'primary',
permission: 'device/Instance:action',
icon: 'CheckCircleOutlined',
popConfirm: {
title: '确认启用全部设备?',
onConfirm: activeAllDevice,
},
},
{
key: 'sync',
text: '同步设备状态',
type: 'primary',
ghost: true,
icon: 'SyncOutlined',
onClick: syncDeviceStatus,
},
{
key: 'delete',
text: '批量删除设备',
danger: true,
permission: 'device/Instance:delete',
icon: 'DeleteOutlined',
selected: {
popConfirm: {
title: '已启用的设备无法删除,确认删除选中的禁用状态设备?',
onConfirm: delSelectedDevice,
},
},
},
// {
// key: 'active',
// text: '',
// ghost: true,
// type: 'primary',
// icon: 'CheckOutlined',
// permission: 'device/Instance:action',
// selected: {
// popConfirm: {
// title: '',
// onConfirm: activeSelectedDevice,
// },
// },
// },
{
key: 'disable',
text: '批量禁用设备',
danger: true,
icon: 'StopOutlined',
permission: 'device/Instance:action',
selected: {
popConfirm: {
title: '确认禁用选中设备?',
onConfirm: disabledSelectedDevice,
}
},
},
];
const saveBtn = () => {
visible.value = false;
instanceRef.value?.reload();

View File

@ -42,12 +42,12 @@ const props = defineProps({
const type = ref<string>('xlsx');
const handleOk = () => {
console.log(props.data);
_export(type.value, props.data).then((res: any) => {
console.log(res)
if (res) {
const blob = new Blob([res], { type: type.value });
const url = URL.createObjectURL(blob);
console.log(url);
console.log(url, 123);
downloadFileByUrl(
url,
`物联卡管理-${moment(new Date()).format(

View File

@ -1,21 +1,44 @@
<!-- 物联卡管理 -->
<template>
<page-container>
<pro-search :columns="columns" target="iot-card-management-search" @search="handleSearch" />
<j-pro-table :scroll="{x: 1366}" ref="cardManageRef" :columns="columns" :request="query"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }" :rowSelection="{
selectedRowKeys: _selectedRowKeys,
onChange: onSelectChange,
}" @cancelSelect="cancelSelect" :params="params" :gridColumn="3">
<pro-search
:columns="columns"
target="iot-card-management-search"
@search="handleSearch"
/>
<j-pro-table
:scroll="{ x: 1366 }"
ref="cardManageRef"
:columns="columns"
:request="query"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
:rowSelection="
isCheck
? {
selectedRowKeys: _selectedRowKeys,
onChange: onSelectChange,
}
: false
"
@cancelSelect="cancelSelect"
:params="params"
:gridColumn="3"
>
<template #headerTitle>
<j-space>
<!-- <a-button type="primary" @click="handleAdd">
<AIcon type="PlusOutlined" />新增
</a-button> -->
<PermissionButton @click="handleAdd" :hasPermission="'iot-card/CardManagement:add'" type="primary">
<PermissionButton
@click="handleAdd"
:hasPermission="'iot-card/CardManagement:add'"
type="primary"
>
<AIcon type="PlusOutlined" />新增
</PermissionButton>
<j-dropdown>
<BatchDropdown
v-model:isCheck="isCheck"
:actions="batchActions"
@change="onCheckChange"
/>
<!-- <j-dropdown>
<j-button>
批量操作
<AIcon type="DownOutlined" />
@ -23,86 +46,122 @@
<template #overlay>
<j-menu>
<j-menu-item>
<PermissionButton @click="exportVisible = true"
:hasPermission="'iot-card/CardManagement:export'">
<PermissionButton
@click="exportVisible = true"
:hasPermission="'iot-card/CardManagement:export'"
>
<AIcon type="ExportOutlined" />
批量导出
</PermissionButton>
</j-menu-item>
<j-menu-item>
<PermissionButton @click="importVisible = true"
:hasPermission="'iot-card/CardManagement:import'">
<PermissionButton
@click="importVisible = true"
:hasPermission="'iot-card/CardManagement:import'"
>
<AIcon type="ImportOutlined" />批量导入
</PermissionButton>
</j-menu-item>
<j-menu-item>
<PermissionButton :popConfirm="{
title: '确认激活吗?',
onConfirm: handleActive,
}" :hasPermission="'iot-card/CardManagement:active'">
<PermissionButton
:popConfirm="{
title: '确认激活吗?',
onConfirm: handleActive,
}"
:hasPermission="'iot-card/CardManagement:active'"
>
<AIcon type="CheckCircleOutlined" />
批量激活
</PermissionButton>
</j-menu-item>
<j-menu-item>
<PermissionButton :popConfirm="{
title: '确认停用吗?',
onConfirm: handleStop,
}" ghost type="primary" :hasPermission="'iot-card/CardManagement:action'">
<PermissionButton
:popConfirm="{
title: '确认停用吗?',
onConfirm: handleStop,
}"
ghost
type="primary"
:hasPermission="'iot-card/CardManagement:action'"
>
<AIcon type="StopOutlined" />
批量停用
</PermissionButton>
</j-menu-item>
<j-menu-item>
<PermissionButton :popConfirm="{
title: '确认复机吗?',
onConfirm: handleResumption,
}" ghost type="primary" :hasPermission="'iot-card/CardManagement:action'">
<PermissionButton
:popConfirm="{
title: '确认复机吗?',
onConfirm: handleResumption,
}"
ghost
type="primary"
:hasPermission="'iot-card/CardManagement:action'"
>
<AIcon type="PoweroffOutlined" />
批量复机
</PermissionButton>
</j-menu-item>
<j-menu-item>
<PermissionButton :popConfirm="{
title: '确认同步状态吗?',
onConfirm: handleSync,
}" ghost type="primary" :hasPermission="'iot-card/CardManagement:sync'">
<PermissionButton
:popConfirm="{
title: '确认同步状态吗?',
onConfirm: handleSync,
}"
ghost
type="primary"
:hasPermission="'iot-card/CardManagement:sync'"
>
<AIcon type="SwapOutlined" />
同步状态
</PermissionButton>
</j-menu-item>
<j-menu-item v-if="_selectedRowKeys.length > 0">
<PermissionButton :popConfirm="{
title: '确认删除吗?',
onConfirm: handelRemove,
}" ghost type="primary" :hasPermission="'iot-card/CardManagement:delete'">
<PermissionButton
:popConfirm="{
title: '确认删除吗?',
onConfirm: handelRemove,
}"
ghost
type="primary"
:hasPermission="'iot-card/CardManagement:delete'"
>
<AIcon type="SwapOutlined" />
批量删除
</PermissionButton>
</j-menu-item>
</j-menu>
</template>
</j-dropdown>
</j-dropdown> -->
</j-space>
</template>
<template #card="slotProps">
<CardBox :value="slotProps" @click="handleClick" :actions="getActions(slotProps, 'card')" v-bind="slotProps"
:active="_selectedRowKeys.includes(slotProps.id)" :status="slotProps.cardStateType.value"
:statusText="slotProps.cardStateType.text" :statusNames="{
<CardBox
:value="slotProps"
@click="handleClick"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:active="_selectedRowKeys.includes(slotProps.id)"
:status="slotProps.cardStateType.value"
:statusText="slotProps.cardStateType.text"
:statusNames="{
using: 'processing',
toBeActivated: 'default',
deactivate: 'error',
}">
}"
>
<template #img>
<slot name="img">
<img :src="getImage('/iot-card/iot-card-bg.png')" />
</slot>
</template>
<template #content>
<h3 class="card-item-content-title">
{{ slotProps.id }}
</h3>
<j-row>
<Ellipsis style="width: calc(100% - 100px)">
<span style="font-size: 16px; font-weight: 600">
{{ slotProps.id }}
</span>
</Ellipsis>
<j-row style="margin-top: 20px">
<j-col :span="8">
<div class="card-item-content-text">
平台对接
@ -114,46 +173,72 @@
<div>{{ slotProps.cardType.text }}</div>
</j-col>
<j-col :span="6">
<div class="card-item-content-text">绑定设备</div>
<div class="card-item-content-text">
绑定设备
</div>
<div>{{ slotProps.deviceName }}</div>
</j-col>
</j-row>
<j-divider style="margin: 12px 0" />
<div class="content-bottom">
<div v-if="slotProps.usedFlow === 0">
<span class="flow-text">
{{ slotProps.totalFlow }}
</span>
<span class="card-item-content-text">
M 使用流量</span>
</div>
<div v-else>
<div class="progress-text">
<div>
{{
(slotProps.usedFlow / slotProps.totalFlow * 100).toFixed(2)
}}
%
</div>
<div class="card-item-content-text">
总共 {{ slotProps.totalFlow }} M
</div>
<span class="flow-text">
{{ slotProps.totalFlow }}
</span>
<span class="card-item-content-text">
M 使用流量</span
>
</div>
<div v-else>
<div class="progress-text">
<div>
{{
(
(slotProps.usedFlow /
slotProps.totalFlow) *
100
).toFixed(2)
}}
%
</div>
<div class="card-item-content-text">
总共 {{ slotProps.totalFlow }} M
</div>
</div>
<j-progress
:strokeColor="'#ADC6FF'"
:showInfo="false"
:percent="
(slotProps.usedFlow /
slotProps.totalFlow) *
100
"
/>
</div>
<j-progress :strokeColor="'#ADC6FF'" :showInfo="false" :percent="slotProps.usedFlow / slotProps.totalFlow * 100" />
</div>
</div>
</template>
<template #actions="item">
<PermissionButton :disabled="item.disabled" :popConfirm="item.popConfirm" :tooltip="{
...item.tooltip,
}" @click="item.onClick" :hasPermission="'iot-card/CardManagement:' + item.key">
<AIcon type="DeleteOutlined" v-if="item.key === 'delete'" />
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="
'iot-card/CardManagement:' + item.key
"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
<!-- <a-tooltip
<!-- <a-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
>
@ -199,8 +284,8 @@
<div>
{{
slotProps.totalFlow
? slotProps.totalFlow.toFixed(2) + ' M'
: ''
? slotProps.totalFlow.toFixed(2) + ' M'
: ''
}}
</div>
</template>
@ -208,8 +293,8 @@
<div>
{{
slotProps.usedFlow
? slotProps.usedFlow.toFixed(2) + ' M'
: ''
? slotProps.usedFlow.toFixed(2) + ' M'
: ''
}}
</div>
</template>
@ -217,8 +302,8 @@
<div>
{{
slotProps.residualFlow
? slotProps.residualFlow.toFixed(2) + ' M'
: ''
? slotProps.residualFlow.toFixed(2) + ' M'
: ''
}}
</div>
</template>
@ -239,28 +324,43 @@
<template #activationDate="slotProps">
{{
slotProps.activationDate
? dayjs(slotProps.activationDate).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
? dayjs(slotProps.activationDate).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}
</template>
<template #updateTime="slotProps">
{{
slotProps.updateTime
? dayjs(slotProps.updateTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
? dayjs(slotProps.updateTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}
</template>
<template #action="slotProps">
<j-space :size="16">
<template v-for="i in getActions(slotProps, 'table')" :key="i.key">
<PermissionButton :disabled="i.disabled" :popConfirm="i.popConfirm" :tooltip="{
...i.tooltip,
}" @click="i.onClick" type="link" style="padding: 0px"
:hasPermission="'iot-card/CardManagement:' + i.key">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
<PermissionButton
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="
i.key === 'view'
? true
: 'iot-card/CardManagement:' + i.key
"
:danger="i.key === 'delete'"
>
<template #icon>
<AIcon :type="i.icon" />
</template>
@ -272,11 +372,24 @@
<!-- 批量导入 -->
<Import v-if="importVisible" @close="importVisible = false" />
<!-- 批量导出 -->
<Export v-if="exportVisible" @close="exportVisible = false" :data="_selectedRowKeys" />
<Export
v-if="exportVisible"
@close="exportVisible = false"
:data="_selectedRowKeys"
/>
<!-- 绑定设备 -->
<BindDevice v-if="bindDeviceVisible" :cardId="cardId" @change="bindDevice" />
<BindDevice
v-if="bindDeviceVisible"
:cardId="cardId"
@change="bindDevice"
/>
<!-- 新增编辑 -->
<Save v-if="visible" :type="saveType" :data="current" @change="saveChange" />
<Save
v-if="visible"
:type="saveType"
:data="current"
@change="saveChange"
/>
</page-container>
</template>
@ -304,11 +417,13 @@ import BindDevice from './BindDevice.vue';
import Import from './Import.vue';
import Export from './Export.vue';
import Save from './Save.vue';
import { useMenuStore } from 'store/menu'
import { useMenuStore } from 'store/menu';
import BadgeStatus from '@/components/BadgeStatus/index.vue';
import BatchDropdown from '@/components/BatchDropdown/index.vue';
import { BatchActionsType } from '@/components/BatchDropdown/types';
const router = useRouter();
const menuStory = useMenuStore()
const menuStory = useMenuStore();
const cardManageRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({});
const _selectedRowKeys = ref<string[]>([]);
@ -320,6 +435,7 @@ const importVisible = ref<boolean>(false);
const cardId = ref<any>();
const current = ref<Partial<CardManagement>>({});
const saveType = ref<string>('');
const isCheck = ref<boolean>(false);
const columns = [
{
@ -472,21 +588,7 @@ const getActions = (
type: 'card' | 'table',
): ActionsType[] => {
if (!data) return [];
return [
{
key: 'view',
text: '查看',
tooltip: {
title: '查看',
},
icon: 'EyeOutlined',
onClick: () => {
// router.push({
// path: `/iot-card/CardManagement/detail/${data.id}`,
// });
menuStory.jumpPage('iot-card/CardManagement/Detail', { id: data.id })
},
},
const arr = [
{
key: 'update',
text: '编辑',
@ -509,18 +611,18 @@ const getActions = (
icon: data.deviceId ? 'DisconnectOutlined' : 'LinkOutlined',
popConfirm: data.deviceId
? {
title: '确认解绑设备?',
okText: '确定',
cancelText: '取消',
onConfirm: async () => {
unbind(data.id).then((resp: any) => {
if (resp.status === 200) {
message.success('操作成功')
cardManageRef.value?.reload();
}
});
},
}
title: '确认解绑设备?',
okText: '确定',
cancelText: '取消',
onConfirm: async () => {
unbind(data.id).then((resp: any) => {
if (resp.status === 200) {
message.success('操作成功');
cardManageRef.value?.reload();
}
});
},
}
: undefined,
onClick: () => {
if (!data.deviceId) {
@ -530,55 +632,58 @@ const getActions = (
},
},
{
key: data.cardStateType?.value === 'toBeActivated' ? 'active' : 'action',
key:
data.cardStateType?.value === 'toBeActivated'
? 'active'
: 'action',
text:
data.cardStateType?.value === 'toBeActivated'
? '激活'
: data.cardStateType?.value === 'deactivate'
? '复机'
: '停用',
? '复机'
: '停用',
tooltip: {
title:
data.cardStateType?.value === 'toBeActivated'
? '激活'
: data.cardStateType?.value === 'deactivate'
? '复机'
: '停用',
? '复机'
: '停用',
},
icon:
data.cardStateType?.value === 'toBeActivated'
? 'CheckCircleOutlined'
: data.cardStateType?.value === 'deactivate'
? 'PoweroffOutlined'
: 'StopOutlined',
? 'PoweroffOutlined'
: 'StopOutlined',
popConfirm: {
title:
data.cardStateType?.value === 'toBeActivated'
? '确认激活?'
: data.cardStateType?.value === 'deactivate'
? '确认复机?'
: '确认停用?',
? '确认复机?'
: '确认停用?',
okText: '确定',
cancelText: '取消',
onConfirm: async () => {
if (data.cardStateType?.value === 'toBeActivated') {
changeDeploy(data.id).then((resp) => {
if (resp.status === 200) {
message.success('操作成功')
message.success('操作成功');
cardManageRef.value?.reload();
}
});
} else if (data.cardStateType?.value === 'deactivate') {
resumption(data.id).then((resp) => {
if (resp.status === 200) {
message.success('操作成功')
message.success('操作成功');
cardManageRef.value?.reload();
}
});
} else {
unDeploy(data.id).then((resp) => {
if (resp.status === 200) {
message.success('操作成功')
message.success('操作成功');
cardManageRef.value?.reload();
}
});
@ -599,7 +704,7 @@ const getActions = (
onConfirm: async () => {
const resp: any = await del(data.id);
if (resp.status === 200) {
message.success('操作成功')
message.success('操作成功');
cardManageRef.value?.reload();
} else {
message.error('操作失败!');
@ -609,6 +714,26 @@ const getActions = (
icon: 'DeleteOutlined',
},
];
if (type === 'card') {
return arr;
} else {
return [
{
key: 'view',
text: '查看',
tooltip: {
title: '查看',
},
icon: 'EyeOutlined',
onClick: () => {
menuStory.jumpPage('iot-card/CardManagement/Detail', {
id: data.id,
});
},
},
...arr,
];
}
};
const handleSearch = (e: any) => {
@ -625,14 +750,24 @@ const cancelSelect = () => {
};
const handleClick = (dt: any) => {
if (_selectedRowKeys.value.includes(dt.id)) {
const _index = _selectedRowKeys.value.findIndex((i) => i === dt.id);
_selectedRowKeys.value.splice(_index, 1);
if (isCheck.value) {
if (_selectedRowKeys.value.includes(dt.id)) {
const _index = _selectedRowKeys.value.findIndex((i) => i === dt.id);
_selectedRowKeys.value.splice(_index, 1);
} else {
_selectedRowKeys.value = [..._selectedRowKeys.value, dt.id];
}
} else {
_selectedRowKeys.value = [..._selectedRowKeys.value, dt.id];
menuStory.jumpPage('iot-card/CardManagement/Detail', {
id: dt.id,
});
}
};
const onCheckChange = () => {
_selectedRowKeys.value = [];
};
/**
* 新增
*/
@ -694,7 +829,7 @@ const handleStop = () => {
) {
unDeployBatch(_selectedRowKeys.value).then((res: any) => {
if (res.status === 200) {
message.success('操作成功')
message.success('操作成功');
}
});
} else {
@ -712,7 +847,7 @@ const handleResumption = () => {
) {
resumptionBatch(_selectedRowKeys.value).then((res: any) => {
if (res.status === 200) {
message.success('操作成功')
message.success('操作成功');
}
});
} else {
@ -736,20 +871,106 @@ const handleSync = () => {
* 批量删除
*/
const handelRemove = async () => {
if (!_selectedRow.value.length) {
message.error('请选择数据');
return;
}
const resp = await removeCards(_selectedRow.value);
if (resp.status === 200) {
message.success('操作成功')
message.success('操作成功');
_selectedRowKeys.value = [];
_selectedRow.value = [];
cardManageRef.value?.reload();
}
};
const batchActions: BatchActionsType[] = [
{
key: 'export',
text: '批量导出',
permission: 'iot-card/CardManagement:export',
icon: 'ExportOutlined',
onClick: () => {
exportVisible.value = true;
},
},
{
key: 'import',
text: '批量导入',
permission: 'iot-card/CardManagement:import',
icon: 'ImportOutlined',
onClick: () => {
importVisible.value = true;
},
},
// {
// key: 'active',
// text: '',
// permission: 'iot-card/CardManagement:active',
// icon: 'CheckCircleOutlined',
// selected: {
// popConfirm: {
// title: '',
// onConfirm: handleActive,
// },
// },
// },
{
key: 'stop',
text: '批量停用',
permission: 'iot-card/CardManagement:action',
icon: 'StopOutlined',
selected: {
popConfirm: {
title: '确认停用吗?',
onConfirm: handleStop,
},
},
},
{
key: 'resumption',
text: '批量复机',
ghost: true,
type: 'primary',
permission: 'iot-card/CardManagement:action',
icon: 'PoweroffOutlined',
selected: {
popConfirm: {
title: '确认复机吗?',
onConfirm: handleResumption,
},
},
},
{
key: 'sync',
text: '同步状态',
ghost: true,
type: 'primary',
permission: 'iot-card/CardManagement:sync',
icon: 'SwapOutlined',
popConfirm: {
title: '确认同步状态吗?',
onConfirm: handleSync,
},
},
{
key: 'delete',
text: '批量删除',
danger: true,
permission: 'iot-card/CardManagement:delete',
icon: 'StopOutlined',
selected: {
popConfirm: {
title: '确认删除吗?',
onConfirm: handelRemove,
},
},
},
];
</script>
<style scoped lang="less">
.content-bottom {
height: 45px;
height: 38px;
}
.flow-text {
font-size: 20px;

View File

@ -8,7 +8,7 @@
:allowClear="false"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
v-model="data.time"
v-model:value="data.time"
>
<template #suffixIcon
><AIcon type="CalendarOutlined"
@ -51,7 +51,7 @@ m
<script lang="ts" setup name="Cpu">
import * as echarts from 'echarts';
import { dashboard } from '@/api/link/dashboard';
import moment from 'moment';
import dayjs from 'dayjs';
import {
getTimeFormat,
getTimeByType,
@ -75,6 +75,8 @@ const pickerTimeChange = () => {
const getCPUEcharts = async (val: any) => {
loading.value = true;
console.log(224, val);
const res: any = await dashboard(defulteParamsData('cpu', val));
if (res.success) {
const _cpuOptions = {};
@ -84,10 +86,11 @@ const getCPUEcharts = async (val: any) => {
const value = item.data.value;
const nodeID = item.data.clusterNodeId;
_cpuXAxis.add(
moment(value.timestamp).format(
dayjs(value.timestamp).format(
getTimeFormat(data.value.type),
),
);
if (!_cpuOptions[nodeID]) {
_cpuOptions[nodeID] = [];
}
@ -163,10 +166,9 @@ const handleCpuOptions = (optionsData: any, xAxis: any) => {
watch(
() => data.value.type,
(val) => {
const endTime = moment(new Date());
const startTime = getTimeByType(val);
data.value.time = [startTime, endTime];
(value) => {
const date = getTimeByType(value);
data.value.time = [dayjs(date), dayjs(new Date())];
},
{ immediate: true, deep: true },
);

View File

@ -8,7 +8,7 @@
:allowClear="false"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
v-model="data.time"
v-model:value="data.time"
>
<template #suffixIcon
><AIcon type="CalendarOutlined"
@ -51,7 +51,7 @@
<script lang="ts" setup name="Jvm">
import * as echarts from 'echarts';
import { dashboard } from '@/api/link/dashboard';
import moment from 'moment';
import dayjs from 'dayjs';
import {
getTimeFormat,
getTimeByType,
@ -95,7 +95,7 @@ const getJVMEcharts = async (val: any) => {
_jvmOptions[nodeID] = [];
}
_jvmXAxis.add(
moment(value.timestamp).format(
dayjs(value.timestamp).format(
getTimeFormat(data.value.type),
),
);
@ -168,10 +168,9 @@ const handleJVMOptions = (optionsData: any, xAxis: any) => {
watch(
() => data.value.type,
(val) => {
const endTime = moment(new Date());
const startTime = getTimeByType(val);
data.value.time = [startTime, endTime];
(value) => {
const date = getTimeByType(value);
data.value.time = [dayjs(date), dayjs(new Date())];
},
{ immediate: true, deep: true },
);

View File

@ -33,7 +33,7 @@
:allowClear="false"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
v-model="data.time.time"
v-model:value="data.time.time"
@change="pickerTimeChange"
>
<template #suffixIcon
@ -65,7 +65,7 @@ import {
areaStyle,
networkParams,
} from './tool.ts';
import moment from 'moment';
import dayjs from 'dayjs';
import * as echarts from 'echarts';
import { DataType } from '../typings.d';
@ -79,7 +79,7 @@ const data = ref<DataType>({
},
});
const isEmpty = ref(false);
const pickerTimeChange = () => {
const pickerTimeChange = (value: any) => {
data.value.time.type = undefined;
};
@ -173,9 +173,8 @@ const handleNetworkOptions = (optionsData: any, xAxis: any) => {
watch(
() => data.value.time.type,
(value) => {
const endTime = moment(new Date());
const startTime = getTimeByType(value);
data.value.time.time = [startTime, endTime];
const date = getTimeByType(value);
data.value.time.time = [dayjs(date), dayjs(new Date())];
},
{ immediate: true, deep: true },
);

View File

@ -1,7 +1,7 @@
import moment from 'moment';
import dayjs from 'dayjs';
import * as echarts from 'echarts';
export const getInterval = (type) => {
export const getInterval = (type: string) => {
switch (type) {
case 'year':
return '30d';
@ -14,7 +14,7 @@ export const getInterval = (type) => {
return '1h';
}
};
export const getTimeFormat = (type) => {
export const getTimeFormat = (type: string) => {
switch (type) {
case 'year':
return 'YYYY-MM-DD';
@ -28,22 +28,22 @@ export const getTimeFormat = (type) => {
}
};
export const getTimeByType = (type) => {
export const getTimeByType = (type: string) => {
switch (type) {
case 'hour':
return moment().subtract(1, 'hours');
return dayjs().subtract(1, 'hours');
case 'week':
return moment().subtract(6, 'days');
return dayjs().subtract(6, 'days');
case 'month':
return moment().subtract(29, 'days');
return dayjs().subtract(29, 'days');
case 'year':
return moment().subtract(365, 'days');
return dayjs().subtract(365, 'days');
default:
return moment().startOf('day');
return dayjs().startOf('day');
}
};
export const arrayReverse = (data) => {
export const arrayReverse = (data: string) => {
const newArray = [];
for (let i = data.length - 1; i >= 0; i--) {
newArray.push(data[i]);
@ -51,7 +51,7 @@ export const arrayReverse = (data) => {
return newArray;
};
export const networkParams = (val) => [
export const networkParams = (val: any) => [
{
dashboard: 'systemMonitor',
object: 'network',
@ -61,12 +61,12 @@ export const networkParams = (val) => [
params: {
type: val.type,
interval: getInterval(val.time.type),
from: moment(val.time.time[0]).valueOf(),
to: moment(val.time.time[1]).valueOf(),
from: dayjs(val.time.time[0]).valueOf(),
to: dayjs(val.time.time[1]).valueOf(),
},
},
];
export const defulteParamsData = (group, val) => [
export const defulteParamsData = (group: any, val: any) => [
{
dashboard: 'systemMonitor',
object: 'stats',
@ -74,8 +74,8 @@ export const defulteParamsData = (group, val) => [
dimension: 'history',
group,
params: {
from: moment(val.time[0]).valueOf(),
to: moment(val.time[1]).valueOf(),
from: dayjs(val.time[0]).valueOf(),
to: dayjs(val.time[1]).valueOf(),
},
},
];

View File

@ -125,6 +125,7 @@
style="padding: 0px"
@click="i.onClick"
type="link"
:danger="i.key === 'delete'"
:hasPermission="'link/Protocol:' + i.key"
>
<template #icon
@ -160,6 +161,7 @@ const columns = [
key: 'id',
search: {
type: 'string',
defaultTermType: 'eq',
},
width: 200,
fixed: 'left',
@ -230,6 +232,9 @@ const getActions = (
{
key: 'delete',
text: '删除',
tooltip: {
title: '删除',
},
popConfirm: {
title: '确认删除?',
onConfirm: async () => {
@ -281,7 +286,6 @@ const handleSearch = (e: any) => {
};
</script>
<style lang="less" scoped>
.card-item-content {
min-height: 100px;

View File

@ -95,13 +95,23 @@
>
<j-collapse-panel
:key="cluster.id"
:header="`#${index + 1}.节点`"
:header="
cluster.serverId
? cluster.serverId
: `#${index + 1}.配置信息`
"
collapsible="header"
>
<template #extra v-if="!shareCluster">
<AIcon
@click="removeCluster(cluster)"
type="DeleteOutlined"
/>
<j-popconfirm
@confirm.prevent="
removeCluster(cluster)
"
>
<span class="delete-btn">
删除
</span>
</j-popconfirm>
</template>
<j-row :gutter="[24, 0]">
<j-col :span="12" v-if="!shareCluster">
@ -558,8 +568,8 @@
'secure',
formData.type,
)
? '开启TLS'
: '开启DTLS'
? '开启DTLS'
: '开启TLS'
"
:name="[
'cluster',
@ -801,7 +811,7 @@
>
<j-monaco-editor
theme="vs"
v-model:value="
v-model:modelValue="
cluster
.configuration
.parserConfiguration
@ -968,7 +978,12 @@
</j-collapse>
</div>
<j-form-item v-if="!shareCluster">
<j-button type="dashed" block @click="addCluster">
<j-button
type="primary"
block
ghost
@click="addCluster"
>
<AIcon type="PlusOutlined" />
新增
</j-button>
@ -1116,6 +1131,7 @@ const changeShareCluster = (value: boolean) => {
};
const changeType = (value: string) => {
getResourcesCurrent();
dynamicValidateForm.cluster = [{ ...cloneDeep(FormStates2), id: '1' }];
if (value !== 'MQTT_CLIENT') {
const { configuration } = dynamicValidateForm.cluster[0];
@ -1170,7 +1186,15 @@ const changeParserType = (value: string | undefined, index: number) => {
const saveData = async () => {
await formRef1.value?.validate();
const formRef2Data = await formRef2.value?.validate();
const formRef2Data = await formRef2.value?.validate().catch((err) => {
err.errorFields.forEach((item: any) => {
const activeId: any = dynamicValidateForm.cluster[item.name[1]].id;
if (!activeKey.value.includes(activeId)) {
activeKey.value.push(activeId); //
}
});
});
const { configuration } = formRef2Data?.cluster[0];
const params = shareCluster.value
@ -1179,9 +1203,8 @@ const saveData = async () => {
loading.value = true;
const resp: any =
id === ':id'
? await save(params).catch(() => {})
: await update({ ...params, id }).catch(() => {});
id === ':id' ? await save(params) : await update({ ...params, id });
loading.value = false;
if (resp?.status === 200) {
onlyMessage('操作成功', 'success');
history.back();
@ -1194,7 +1217,6 @@ const saveData = async () => {
}
}
}
loading.value = false;
};
const getSupports = async () => {
@ -1243,6 +1265,10 @@ const getDetail = () => {
} else {
dynamicValidateForm.cluster = cluster;
}
if (dynamicValidateForm.cluster.length === 1) {
dynamicValidateForm.cluster[0].id = '1';
}
}
});
loading.value = false;
@ -1286,6 +1312,7 @@ watch(
},
{ deep: true, immediate: true },
);
watch(
() => NetworkType,
(value) => {
@ -1343,4 +1370,12 @@ watch(
margin-bottom: 20px;
background: #f4f4f4;
}
.delete-btn {
display: inline-block;
color: #e50012;
padding: 0px 8px;
background: #ffffff;
border: 1px solid #e50012;
border-radius: 2px;
}
</style>

View File

@ -63,7 +63,7 @@ export const VisibleData = {
serverId: ['MQTT_CLIENT'],
remoteHost: ['MQTT_CLIENT'],
remotePort: ['MQTT_CLIENT'],
secure: ['TCP_SERVER', 'UDP', 'COAP_SERVER'],
secure: ['UDP', 'COAP_SERVER'],
username: ['MQTT_CLIENT'],
password: ['MQTT_CLIENT'],
topicPrefix: ['MQTT_CLIENT'],
@ -160,7 +160,7 @@ export const Rules = {
},
{
pattern: Validator.regIp || Validator.regDomain,
message: '请输入IP或者域名',
message: '请输入正确格式的域名或ip',
},
],
publicPort: [
@ -180,7 +180,7 @@ export const Rules = {
},
{
pattern: Validator.regIp || Validator.regDomain,
message: '请输入IP或者域名',
message: '请输入正确格式的域名或ip',
},
],
remotePort: [
@ -215,7 +215,8 @@ export const Rules = {
],
password: [
{
required: true,
// required: true,
required: false,
message: '请输入密码',
},
{

View File

@ -157,7 +157,7 @@
</template>
<template #shareCluster="slotProps">
{{
slotProps.shareCluster === true
slotProps.shareCluster === 'true'
? '共享配置'
: '独立配置'
}}
@ -185,10 +185,6 @@ const tableRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({});
const options = ref([]);
// const statusMap = new Map();
// statusMap.set('enabled', 'processing');
// statusMap.set('disabled', 'error');
const columns = [
{
title: '名称',
@ -223,8 +219,8 @@ const columns = [
search: {
type: 'select',
options: [
{ label: '共享配置', value: true },
{ label: '独立配置', value: false },
{ label: '共享配置', value: 'true' },
{ label: '独立配置', value: 'false' },
],
},
},
@ -255,6 +251,9 @@ const columns = [
dataIndex: 'description',
key: 'description',
ellipsis: true,
search: {
type: 'string',
},
},
{
title: '操作',
@ -367,8 +366,13 @@ const getDetails = (slotProps: Partial<Record<string, any>>) => {
':' +
(cluster[0].configuration.publicPort ||
cluster[0].configuration.remotePort);
return headers + content;
let head = '远程:';
if (!!shareCluster) {
!!configuration.publicHost && (head = '公网:');
} else {
!!cluster[0].configuration.publicHos && (head = '公网:');
}
return head + headers + content;
};
const getSupports = async () => {