Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev
This commit is contained in:
commit
a8af608299
|
@ -217,3 +217,28 @@ export const queryMetric = (deviceId: string, propertyId: string) => server.get(
|
|||
* @returns
|
||||
*/
|
||||
export const saveMetric = (deviceId: string, propertyId: string, data: Record<string, any>) => server.patch(`/device-instance/${deviceId}/metric/property/${propertyId}`, data)
|
||||
|
||||
/**
|
||||
* 解绑子设备
|
||||
* @param deviceId 设备id
|
||||
* @param childrenId 子设备id
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const unbindDevice = (deviceId: string, childrenId: string, data: Record<string, any>) => server.post(`/device/gateway/${deviceId}/unbind/${childrenId}`, data)
|
||||
|
||||
/**
|
||||
* 批量解绑子设备
|
||||
* @param deviceId 设备id
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const unbindBatchDevice = (deviceId: string, data: Record<string, any>) => server.post(`/device/gateway/${deviceId}/unbind`, data)
|
||||
|
||||
/**
|
||||
* 子设备绑定
|
||||
* @param deviceId 设备id
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const bindDevice = (deviceId: string, data: Record<string, any>) => server.post(`/device/gateway/${deviceId}/bind`, data)
|
||||
|
|
|
@ -6,10 +6,14 @@ export const getRoleList_api = (data: any): Promise<any> => server.post(`/role/_
|
|||
export const delRole_api = (id: string): Promise<any> => server.remove(`/role/${id}`);
|
||||
// 保存角色
|
||||
export const saveRole_api = (data: any): Promise<any> => server.post(`/role`, data);
|
||||
// 更新角色信息
|
||||
export const updateRole_api = (data: any): Promise<any> => server.patch(`/role`, data);
|
||||
// 获取角色详细信息
|
||||
export const getRoleDetails_api = (id: string): Promise<any> => server.get(`/role/${id}`);
|
||||
// 获取角色对应的权限树
|
||||
export const getPrimissTree_api = (id: string): Promise<any> => server.get(`/menu/role/${id}/_grant/tree`);
|
||||
// 更新角色对应的权限树
|
||||
export const updatePrimissTree_api = (id: string, data:object): Promise<any> => server.put(`/menu/role/${id}/_grant`,data);
|
||||
|
||||
|
||||
// 获取用户列表
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
<!-- 绑定设备 -->
|
||||
<template>
|
||||
<a-modal
|
||||
:maskClosable="false"
|
||||
width="1000px"
|
||||
:visible="true"
|
||||
title="绑定子设备"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
:confirmLoading="btnLoading"
|
||||
>
|
||||
<div style="margin-top: 10px">
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="child-device-bind"
|
||||
@search="handleSearch"
|
||||
type="simple"
|
||||
/>
|
||||
<JTable
|
||||
ref="bindDeviceRef"
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
model="TABLE"
|
||||
:defaultParams="{
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{ column: 'parentId$isnull', value: '1' },
|
||||
{
|
||||
column: 'parentId$not',
|
||||
value: detail.id,
|
||||
type: 'or',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'id$not',
|
||||
value: detail.id,
|
||||
type: 'and',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
termType: 'eq',
|
||||
column: 'deviceType',
|
||||
value: 'childrenDevice',
|
||||
},
|
||||
],
|
||||
type: 'and',
|
||||
},
|
||||
],
|
||||
}"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
}"
|
||||
@cancelSelect="cancelSelect"
|
||||
:params="params"
|
||||
>
|
||||
<template #registryTime="slotProps">
|
||||
{{
|
||||
slotProps.registryTime
|
||||
? moment(slotProps.registryTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
: ''
|
||||
}}
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
:text="slotProps.state.text"
|
||||
:status="statusMap.get(slotProps.state.value)"
|
||||
/>
|
||||
</template>
|
||||
</JTable>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { query, bindDevice } from '@/api/device/instance';
|
||||
import moment from 'moment';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
const { detail } = storeToRefs(instanceStore);
|
||||
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
const bindDeviceRef = ref<Record<string, any>>({});
|
||||
const params = ref<Record<string, any>>({});
|
||||
const _selectedRowKeys = ref<string[]>([]);
|
||||
const btnLoading = ref<boolean>(false);
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('online', 'success');
|
||||
statusMap.set('offline', 'error');
|
||||
statusMap.set('notActive', 'warning');
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '设备名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '所属产品',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '注册时间',
|
||||
dataIndex: 'registryTime',
|
||||
key: 'registryTime',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '禁用', value: 'notActive' },
|
||||
{ label: '离线', value: 'offline' },
|
||||
{ label: '在线', value: 'online' },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const handleSearch = (e: any) => {
|
||||
params.value = e;
|
||||
};
|
||||
|
||||
const onSelectChange = (keys: string[], rows: string[]) => {
|
||||
_selectedRowKeys.value = [...keys];
|
||||
};
|
||||
|
||||
const cancelSelect = () => {
|
||||
_selectedRowKeys.value = [];
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
if (_selectedRowKeys.value.length === 0) {
|
||||
message.warning('请选择需要绑定的设备');
|
||||
return;
|
||||
}
|
||||
btnLoading.value = true;
|
||||
bindDevice(detail.value.id, _selectedRowKeys.value)
|
||||
.then((resp) => {
|
||||
emit('change', true);
|
||||
cancelSelect();
|
||||
message.success('操作成功');
|
||||
})
|
||||
.finally(() => {
|
||||
btnLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('change', false);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
|
@ -0,0 +1,265 @@
|
|||
<template>
|
||||
<a-card>
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="child-device"
|
||||
@search="handleSearch"
|
||||
class="child-device-search"
|
||||
/>
|
||||
<JTable
|
||||
ref="childDeviceRef"
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
:defaultParams="{
|
||||
terms: [
|
||||
{
|
||||
column: 'parentId',
|
||||
value: detail?.id || '',
|
||||
termType: 'eq',
|
||||
},
|
||||
],
|
||||
}"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
}"
|
||||
@cancelSelect="cancelSelect"
|
||||
:params="params"
|
||||
:model="'TABLE'"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
<a-button type="primary"> 新增并绑定 </a-button>
|
||||
<a-button type="primary" @click="visible = true">
|
||||
绑定
|
||||
</a-button>
|
||||
<a-popconfirm title="确认解绑吗?" @confirm="handleUnBind">
|
||||
<a-button type="primary"> 批量解绑 </a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #registryTime="slotProps">
|
||||
{{
|
||||
slotProps.registryTime
|
||||
? moment(slotProps.registryTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
: ''
|
||||
}}
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
:text="slotProps.state.text"
|
||||
:status="statusMap.get(slotProps.state.value)"
|
||||
/>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps)"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm v-if="i.popConfirm" v-bind="i.popConfirm">
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
<BindChildDevice v-if="visible" @change="closeBindDevice" />
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import moment from 'moment';
|
||||
import type { ActionsType } from '@/components/Table';
|
||||
import { query, unbindDevice, unbindBatchDevice } from '@/api/device/instance';
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { message } from 'ant-design-vue';
|
||||
import BindChildDevice from './BindChildDevice/index.vue';
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
const { detail } = storeToRefs(instanceStore);
|
||||
const router = useRouter();
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('online', 'success');
|
||||
statusMap.set('offline', 'error');
|
||||
statusMap.set('notActive', 'warning');
|
||||
|
||||
const childDeviceRef = ref<Record<string, any>>({});
|
||||
const params = ref<Record<string, any>>({});
|
||||
const _selectedRowKeys = ref<string[]>([]);
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '设备名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '所属产品',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '注册时间',
|
||||
dataIndex: 'registryTime',
|
||||
key: 'registryTime',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '禁用', value: 'notActive' },
|
||||
{ label: '离线', value: 'offline' },
|
||||
{ label: '在线', value: 'online' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'describe',
|
||||
key: 'describe',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
|
||||
const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
|
||||
if (!data) return [];
|
||||
return [
|
||||
{
|
||||
key: 'view',
|
||||
text: '查看',
|
||||
tooltip: {
|
||||
title: '查看',
|
||||
},
|
||||
icon: 'EyeOutlined',
|
||||
onClick: () => {
|
||||
router.push('/iot/device/instance/detail/' + data.id);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'unbind',
|
||||
text: '解绑',
|
||||
tooltip: {
|
||||
title: '解绑',
|
||||
},
|
||||
icon: 'DisconnectOutlined',
|
||||
popConfirm: {
|
||||
title: '确认解绑吗?',
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onConfirm: async () => {
|
||||
const resp = await unbindDevice(
|
||||
detail.value.id,
|
||||
data.id,
|
||||
{},
|
||||
);
|
||||
if (resp.status === 200) {
|
||||
childDeviceRef.value?.reload();
|
||||
message.success('操作成功!');
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const handleSearch = (e: any) => {
|
||||
params.value = e;
|
||||
};
|
||||
|
||||
const onSelectChange = (keys: string[]) => {
|
||||
_selectedRowKeys.value = [...keys];
|
||||
};
|
||||
|
||||
const cancelSelect = () => {
|
||||
_selectedRowKeys.value = [];
|
||||
};
|
||||
|
||||
const handleUnBind = async () => {
|
||||
if (_selectedRowKeys.value.length) {
|
||||
const resp = await unbindBatchDevice(
|
||||
detail.value.id,
|
||||
_selectedRowKeys.value,
|
||||
);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
cancelSelect();
|
||||
childDeviceRef.value?.reload();
|
||||
}
|
||||
} else {
|
||||
message.warning('请勾选需要解绑的数据');
|
||||
}
|
||||
};
|
||||
|
||||
const closeBindDevice = (val: boolean) => {
|
||||
visible.value = false;
|
||||
if (val) {
|
||||
childDeviceRef.value?.reload();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.child-device-search {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
:deep(._jtable-body_1eyxz_1 ._jtable-body-header_1eyxz_6) {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
|
@ -43,6 +43,7 @@ import { useInstanceStore } from '@/store/instance';
|
|||
import Info from './Info/index.vue';
|
||||
import Running from './Running/index.vue'
|
||||
import Metadata from '../../components/Metadata/index.vue';
|
||||
import ChildDevice from './ChildDevice/index.vue';
|
||||
import { _deploy, _disconnect } from '@/api/device/instance'
|
||||
import { message } from 'ant-design-vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
|
@ -67,13 +68,18 @@ const list = [
|
|||
{
|
||||
key: 'Metadata',
|
||||
tab: '物模型'
|
||||
},
|
||||
{
|
||||
key: 'ChildDevice',
|
||||
tab: '子设备'
|
||||
}
|
||||
]
|
||||
|
||||
const tabs = {
|
||||
Info,
|
||||
Metadata,
|
||||
Running
|
||||
Running,
|
||||
ChildDevice,
|
||||
}
|
||||
|
||||
watch(
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
}"
|
||||
@cancelSelect="cancelSelect"
|
||||
:params="params"
|
||||
:gridColumn="3"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
<Guide title="流量统计">
|
||||
<template #extra>
|
||||
<TimeSelect
|
||||
key="flow-static"
|
||||
:type="'week'"
|
||||
:quickBtnList="quickBtnList"
|
||||
@change="getEcharts"
|
||||
|
@ -93,6 +94,7 @@
|
|||
<Guide title="流量使用TOP10">
|
||||
<template #extra>
|
||||
<TimeSelect
|
||||
key="flow-top10"
|
||||
:quickBtn="false"
|
||||
:type="'week'"
|
||||
@change="getTopRang"
|
||||
|
|
|
@ -1,85 +1,90 @@
|
|||
<!-- 物联卡-首页 -->
|
||||
<template>
|
||||
<page-container>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="14">
|
||||
<div class="home-guide">
|
||||
<Guide title="物联卡引导"></Guide>
|
||||
<div
|
||||
class="home-guide-items"
|
||||
:style="`grid-template-columns: repeat(${
|
||||
guideList ? guideList.length : 1
|
||||
}, 1fr);`"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in guideList"
|
||||
:key="index"
|
||||
class="home-guide-item step-bar arrow-2 pointer"
|
||||
@click="jumpPage(item)"
|
||||
>
|
||||
<div class="item-english">{{ item.english }}</div>
|
||||
<div class="item-title">{{ item.name }}</div>
|
||||
<div class="item-index">
|
||||
<img :src="Image[index + 1]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="10">
|
||||
<div class="home-statistics">
|
||||
<Guide title="基础统计">
|
||||
<template #extra>
|
||||
<span class="extra-text">详情</span>
|
||||
</template>
|
||||
</Guide>
|
||||
<div class="home-statistics-body">
|
||||
<div class="home-guide-item">
|
||||
<div class="item-english">昨日流量统计</div>
|
||||
<div class="item-title">{{ currentSource }} M</div>
|
||||
<div
|
||||
class="item-index-echarts"
|
||||
style="height: 75px; width: 110px"
|
||||
>
|
||||
<div class="chart" ref="todayFlowChart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="home-guide-item">
|
||||
<div class="item-english">物联卡</div>
|
||||
<div class="item-content">
|
||||
<div
|
||||
v-for="iten in pieChartData"
|
||||
:key="iten.key"
|
||||
class="item-node"
|
||||
>
|
||||
<div class="item-node-text">
|
||||
{{ iten.value }}
|
||||
</div>
|
||||
<div :class="`state ${iten.className}`">
|
||||
{{ iten.name }}
|
||||
</div>
|
||||
<page-container>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="14">
|
||||
<div class="home-guide">
|
||||
<Guide title="物联卡引导"></Guide>
|
||||
<div
|
||||
class="home-guide-items"
|
||||
:style="`grid-template-columns: repeat(${
|
||||
guideList ? guideList.length : 1
|
||||
}, 1fr);`"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in guideList"
|
||||
:key="index"
|
||||
class="home-guide-item step-bar arrow-2 pointer"
|
||||
@click="jumpPage(item)"
|
||||
>
|
||||
<div class="item-english">{{ item.english }}</div>
|
||||
<div class="item-title">{{ item.name }}</div>
|
||||
<div class="item-index">
|
||||
<img :src="Image[index + 1]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="item-index-echarts"
|
||||
style="height: 75px; width: 110px"
|
||||
>
|
||||
<div class="chart" ref="iotCardChart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="24" style="min-height: 580px">
|
||||
<div class="home-body">
|
||||
<Guide title="平台架构图" english="PLATFORM ARCHITECTURE DIAGRAM" />
|
||||
<div class="home-body-img">
|
||||
<img :src="getImage('/iot-card/iotcard-home.png')" />
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</page-container>
|
||||
</a-col>
|
||||
<a-col :span="10">
|
||||
<div class="home-statistics">
|
||||
<Guide title="基础统计">
|
||||
<template #extra>
|
||||
<span class="extra-text" @click="jumpDashboard"
|
||||
>详情</span
|
||||
>
|
||||
</template>
|
||||
</Guide>
|
||||
<div class="home-statistics-body">
|
||||
<div class="home-guide-item">
|
||||
<div class="item-english">昨日流量统计</div>
|
||||
<div class="item-title">{{ currentSource }} M</div>
|
||||
<div
|
||||
class="item-index-echarts"
|
||||
style="height: 75px; width: 110px"
|
||||
>
|
||||
<div class="chart" ref="todayFlowChart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="home-guide-item">
|
||||
<div class="item-english">物联卡</div>
|
||||
<div class="item-content">
|
||||
<div
|
||||
v-for="iten in pieChartData"
|
||||
:key="iten.key"
|
||||
class="item-node"
|
||||
>
|
||||
<div class="item-node-text">
|
||||
{{ iten.value }}
|
||||
</div>
|
||||
<div :class="`state ${iten.className}`">
|
||||
{{ iten.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="item-index-echarts"
|
||||
style="height: 75px; width: 110px"
|
||||
>
|
||||
<div class="chart" ref="iotCardChart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="24" style="min-height: 580px">
|
||||
<div class="home-body">
|
||||
<Guide
|
||||
title="平台架构图"
|
||||
english="PLATFORM ARCHITECTURE DIAGRAM"
|
||||
/>
|
||||
<div class="home-body-img">
|
||||
<img :src="getImage('/iot-card/iotcard-home.png')" />
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -89,220 +94,245 @@ import { message } from 'ant-design-vue';
|
|||
import moment from 'moment';
|
||||
import { queryFlow, list } from '@/api/iot-card/home';
|
||||
import * as echarts from 'echarts';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import { usePermissionStore } from '@/store/permission';
|
||||
|
||||
const router = useRouter();
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
|
||||
interface GuideItemProps {
|
||||
key: string;
|
||||
name: string;
|
||||
english: string;
|
||||
url: string;
|
||||
param?: Record<string, any>;
|
||||
index?: number;
|
||||
auth: boolean;
|
||||
key: string;
|
||||
name: string;
|
||||
english: string;
|
||||
url: string;
|
||||
param?: Record<string, any>;
|
||||
index?: number;
|
||||
auth: boolean;
|
||||
}
|
||||
|
||||
const menuHasPermission = useMenuStore().hasPermission;
|
||||
const btnHasPermission = usePermissionStore().hasPermission;
|
||||
|
||||
// 菜单权限
|
||||
const dashBoardUrl = menuHasPermission('/iot-card/Dashboard');
|
||||
const platformUrl = menuHasPermission('/iot-card/Platform/Detail');
|
||||
const recordUrl = menuHasPermission('/iot-card/Record');
|
||||
const cardUrl = menuHasPermission('/iot-card/CardManagement');
|
||||
|
||||
// 按钮权限
|
||||
const paltformPermission = btnHasPermission(`/iot-card/Platform:add`);
|
||||
const cardPermission = btnHasPermission(`/iot-card/CardManagement:add`);
|
||||
|
||||
const Image = {
|
||||
1: getImage('/home/1.png'),
|
||||
2: getImage('/home/2.png'),
|
||||
3: getImage('/home/3.png'),
|
||||
1: getImage('/home/1.png'),
|
||||
2: getImage('/home/2.png'),
|
||||
3: getImage('/home/3.png'),
|
||||
};
|
||||
const guideList = [
|
||||
{
|
||||
key: 'EQUIPMENT',
|
||||
name: '平台对接',
|
||||
english: 'STEP1',
|
||||
auth: '',
|
||||
url: '',
|
||||
},
|
||||
{
|
||||
key: 'SCREEN',
|
||||
name: '物联卡管理',
|
||||
english: 'STEP2',
|
||||
auth: '',
|
||||
url: '',
|
||||
param: { save: true },
|
||||
},
|
||||
{
|
||||
key: 'CASCADE',
|
||||
name: '操作记录',
|
||||
english: 'STEP3',
|
||||
auth: '',
|
||||
url: '',
|
||||
},
|
||||
{
|
||||
key: 'EQUIPMENT',
|
||||
name: '平台对接',
|
||||
english: 'STEP1',
|
||||
auth: paltformPermission,
|
||||
url: platformUrl,
|
||||
},
|
||||
{
|
||||
key: 'SCREEN',
|
||||
name: '物联卡管理',
|
||||
english: 'STEP2',
|
||||
auth: !!cardPermission,
|
||||
url: cardUrl,
|
||||
param: { save: true },
|
||||
},
|
||||
{
|
||||
key: 'CASCADE',
|
||||
name: '操作记录',
|
||||
english: 'STEP3',
|
||||
auth: !!recordUrl,
|
||||
url: recordUrl,
|
||||
},
|
||||
];
|
||||
|
||||
const currentSource = ref<number>(0);
|
||||
const barChartData = ref<any[]>([]);
|
||||
const pieChartData = ref<any[]>([
|
||||
{
|
||||
key: 'using',
|
||||
name: '正常',
|
||||
value: 0,
|
||||
className: 'normal',
|
||||
},
|
||||
{
|
||||
key: 'toBeActivated',
|
||||
name: '未激活',
|
||||
value: 0,
|
||||
className: 'notActive',
|
||||
},
|
||||
{
|
||||
key: 'deactivate',
|
||||
name: '停用',
|
||||
value: 0,
|
||||
className: 'stopped',
|
||||
},
|
||||
{
|
||||
key: 'using',
|
||||
name: '正常',
|
||||
value: 0,
|
||||
className: 'normal',
|
||||
},
|
||||
{
|
||||
key: 'toBeActivated',
|
||||
name: '未激活',
|
||||
value: 0,
|
||||
className: 'notActive',
|
||||
},
|
||||
{
|
||||
key: 'deactivate',
|
||||
name: '停用',
|
||||
value: 0,
|
||||
className: 'stopped',
|
||||
},
|
||||
]);
|
||||
|
||||
const jumpPage = (data: GuideItemProps) => {
|
||||
if (data.url && data.auth) {
|
||||
router.push(`${data.url}`, data.param);
|
||||
} else {
|
||||
message.warning('暂无权限,请联系管理员');
|
||||
}
|
||||
if (data.url && data.auth) {
|
||||
router.push({ path: `${data.url}`, ...data.param });
|
||||
} else {
|
||||
message.warning('暂无权限,请联系管理员');
|
||||
}
|
||||
};
|
||||
|
||||
const jumpDashboard = () => {
|
||||
if (dashBoardUrl) {
|
||||
router.push(`${dashBoardUrl}`);
|
||||
} else {
|
||||
message.warning('暂无权限,请联系管理员');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取昨日流量消耗
|
||||
*/
|
||||
const getTodayFlow = async () => {
|
||||
const beginTime = moment().subtract(1, 'days').startOf('day').valueOf();
|
||||
const endTime = moment().subtract(1, 'days').endOf('day').valueOf();
|
||||
const resp: any = await queryFlow(beginTime, endTime, { orderBy: 'date' });
|
||||
resp.result.map((item: any) => {
|
||||
currentSource.value += parseFloat(item.value.toFixed(2));
|
||||
});
|
||||
const beginTime = moment().subtract(1, 'days').startOf('day').valueOf();
|
||||
const endTime = moment().subtract(1, 'days').endOf('day').valueOf();
|
||||
const resp: any = await queryFlow(beginTime, endTime, { orderBy: 'date' });
|
||||
resp.result.map((item: any) => {
|
||||
currentSource.value += parseFloat(item.value.toFixed(2));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取最近15天流量消耗统计图数据
|
||||
*/
|
||||
const get15DaysTrafficConsumption = async () => {
|
||||
const beginTime = moment().subtract(15, 'days').startOf('day').valueOf();
|
||||
const endTime = moment().subtract(1, 'days').endOf('day').valueOf();
|
||||
const resp: any = await queryFlow(beginTime, endTime, { orderBy: 'date' });
|
||||
barChartData.value = resp.result
|
||||
.map((item: any) => ({
|
||||
...item,
|
||||
}))
|
||||
.reverse();
|
||||
createBarChart();
|
||||
const beginTime = moment().subtract(15, 'days').startOf('day').valueOf();
|
||||
const endTime = moment().subtract(1, 'days').endOf('day').valueOf();
|
||||
const resp: any = await queryFlow(beginTime, endTime, { orderBy: 'date' });
|
||||
barChartData.value = resp.result
|
||||
.map((item: any) => ({
|
||||
...item,
|
||||
}))
|
||||
.reverse();
|
||||
createBarChart();
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取物联卡状态数据
|
||||
*/
|
||||
const getStateCard = async () => {
|
||||
Promise.all(
|
||||
pieChartData.value.map((item) => {
|
||||
const params = {
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'cardStateType',
|
||||
termType: 'eq',
|
||||
value: item.key,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
return list(params);
|
||||
}),
|
||||
)
|
||||
.then((resp) => {
|
||||
resp.forEach((i: any, index) => {
|
||||
if (i.success) {
|
||||
pieChartData.value[index].value = i.result.total;
|
||||
}
|
||||
});
|
||||
createPieChart();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
Promise.all(
|
||||
pieChartData.value.map((item) => {
|
||||
const params = {
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'cardStateType',
|
||||
termType: 'eq',
|
||||
value: item.key,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
return list(params);
|
||||
}),
|
||||
)
|
||||
.then((resp) => {
|
||||
resp.forEach((i: any, index) => {
|
||||
if (i.success) {
|
||||
pieChartData.value[index].value = i.result.total;
|
||||
}
|
||||
});
|
||||
createPieChart();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 最近15天流量消耗统计图
|
||||
*/
|
||||
const createBarChart = () => {
|
||||
const myChart = echarts.init(proxy.$refs.todayFlowChart);
|
||||
const myChart = echarts.init(proxy.$refs.todayFlowChart);
|
||||
|
||||
const options = {
|
||||
tooltip: {},
|
||||
xAxis: {
|
||||
show: false,
|
||||
data: barChartData.value.map((m) => m.date),
|
||||
},
|
||||
yAxis: {
|
||||
show: false,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '流量消耗',
|
||||
type: 'bar',
|
||||
color: '#FACD89',
|
||||
// barWidth: '5%', // 设单柱状置宽度
|
||||
showBackground: true, //设置柱状的背景虚拟
|
||||
data: barChartData.value.map((m) => parseFloat(m.value.toFixed(2))),
|
||||
},
|
||||
],
|
||||
};
|
||||
myChart.setOption(options);
|
||||
window.addEventListener('resize', function () {
|
||||
myChart.resize();
|
||||
});
|
||||
const options = {
|
||||
tooltip: {},
|
||||
xAxis: {
|
||||
show: false,
|
||||
data: barChartData.value.map((m) => m.date),
|
||||
},
|
||||
yAxis: {
|
||||
show: false,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '流量消耗',
|
||||
type: 'bar',
|
||||
color: '#FACD89',
|
||||
// barWidth: '5%', // 设单柱状置宽度
|
||||
showBackground: true, //设置柱状的背景虚拟
|
||||
data: barChartData.value.map((m) =>
|
||||
parseFloat(m.value.toFixed(2)),
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
myChart.setOption(options);
|
||||
window.addEventListener('resize', function () {
|
||||
myChart.resize();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 物联卡饼图
|
||||
*/
|
||||
const createPieChart = () => {
|
||||
nextTick(() => {
|
||||
const myChart = echarts.init(proxy.$refs.iotCardChart);
|
||||
nextTick(() => {
|
||||
const myChart = echarts.init(proxy.$refs.iotCardChart);
|
||||
|
||||
const options = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c} ({d}%)',
|
||||
},
|
||||
color: ['#85a5ff', '#f29b55', '#c4c4c4'],
|
||||
series: [
|
||||
{
|
||||
name: '',
|
||||
type: 'pie',
|
||||
avoidLabelOverlap: true, //是否启用防止标签重叠策略
|
||||
radius: ['50%', '90%'],
|
||||
center: ['50%', '50%'],
|
||||
itemStyle: {
|
||||
borderColor: 'rgba(255,255,255,1)',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
show: false,
|
||||
const options = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c} ({d}%)',
|
||||
},
|
||||
},
|
||||
data: pieChartData.value,
|
||||
},
|
||||
],
|
||||
};
|
||||
myChart.setOption(options);
|
||||
window.addEventListener('resize', function () {
|
||||
myChart.resize();
|
||||
color: ['#85a5ff', '#f29b55', '#c4c4c4'],
|
||||
series: [
|
||||
{
|
||||
name: '',
|
||||
type: 'pie',
|
||||
avoidLabelOverlap: true, //是否启用防止标签重叠策略
|
||||
radius: ['50%', '90%'],
|
||||
center: ['50%', '50%'],
|
||||
itemStyle: {
|
||||
borderColor: 'rgba(255,255,255,1)',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
data: pieChartData.value,
|
||||
},
|
||||
],
|
||||
};
|
||||
myChart.setOption(options);
|
||||
window.addEventListener('resize', function () {
|
||||
myChart.resize();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
barChartData.value,
|
||||
() => {
|
||||
createBarChart();
|
||||
},
|
||||
{ deep: true },
|
||||
barChartData.value,
|
||||
() => {
|
||||
createBarChart();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
getTodayFlow();
|
||||
|
@ -312,230 +342,230 @@ getStateCard();
|
|||
|
||||
<style scoped lang="less">
|
||||
.home-base {
|
||||
position: relative;
|
||||
padding: 24px 16px;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
padding: 24px 16px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.home-guide {
|
||||
margin-bottom: 24px;
|
||||
padding: 24px 16px;
|
||||
background-color: #fff;
|
||||
margin-bottom: 24px;
|
||||
padding: 24px 16px;
|
||||
background-color: #fff;
|
||||
|
||||
.home-guide-items {
|
||||
display: grid;
|
||||
grid-column-gap: 56px;
|
||||
}
|
||||
.home-guide-items {
|
||||
display: grid;
|
||||
grid-column-gap: 56px;
|
||||
}
|
||||
}
|
||||
|
||||
.home-guide-item {
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
background: linear-gradient(
|
||||
135.62deg,
|
||||
#f6f7fd 22.27%,
|
||||
rgba(255, 255, 255, 0.86) 91.82%
|
||||
);
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 4px 18px #efefef;
|
||||
.state {
|
||||
position: relative;
|
||||
padding-left: 8px;
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 0;
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin-right: 2px;
|
||||
content: '';
|
||||
padding: 16px;
|
||||
background: linear-gradient(
|
||||
135.62deg,
|
||||
#f6f7fd 22.27%,
|
||||
rgba(255, 255, 255, 0.86) 91.82%
|
||||
);
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 4px 18px #efefef;
|
||||
.state {
|
||||
position: relative;
|
||||
padding-left: 8px;
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 0;
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin-right: 2px;
|
||||
content: '';
|
||||
}
|
||||
&.normal::before {
|
||||
background: #85a5ff;
|
||||
}
|
||||
&.notActive::before {
|
||||
background: #f29b55;
|
||||
}
|
||||
&.stopped::before {
|
||||
background: #c4c4c4;
|
||||
}
|
||||
}
|
||||
&.normal::before {
|
||||
background: #85a5ff;
|
||||
|
||||
&.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
&.notActive::before {
|
||||
background: #f29b55;
|
||||
|
||||
.item-english {
|
||||
color: #4f4f4f;
|
||||
}
|
||||
&.stopped::before {
|
||||
background: #c4c4c4;
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
margin-top: 15px;
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
&.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.item-node {
|
||||
min-width: 58px;
|
||||
margin-right: 8px;
|
||||
z-index: 1;
|
||||
|
||||
.item-english {
|
||||
color: #4f4f4f;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
margin-top: 15px;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.item-node {
|
||||
min-width: 58px;
|
||||
margin-right: 8px;
|
||||
z-index: 1;
|
||||
|
||||
.item-node-text {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
.item-node-text {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-title {
|
||||
margin: 20px 0;
|
||||
color: @text-color;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.item-index {
|
||||
position: absolute;
|
||||
right: 10%;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.item-index-echarts {
|
||||
.item-index;
|
||||
|
||||
right: 12px;
|
||||
bottom: 5%;
|
||||
z-index: 0;
|
||||
width: 50%;
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.item-title {
|
||||
margin: 20px 0;
|
||||
color: @text-color;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.item-index {
|
||||
position: absolute;
|
||||
right: 10%;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.item-index-echarts {
|
||||
.item-index;
|
||||
|
||||
right: 12px;
|
||||
bottom: 5%;
|
||||
z-index: 0;
|
||||
width: 50%;
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-body {
|
||||
.home-base;
|
||||
.home-base;
|
||||
|
||||
min-height: 444px;
|
||||
margin-bottom: 24px;
|
||||
// padding-bottom: 26.5%;
|
||||
padding-bottom: 30%;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid #2f54eb;
|
||||
min-height: 444px;
|
||||
margin-bottom: 24px;
|
||||
// padding-bottom: 26.5%;
|
||||
padding-bottom: 30%;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid #2f54eb;
|
||||
|
||||
.home-body-img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.home-body-img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
> img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
> img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-statistics {
|
||||
.home-base;
|
||||
.home-base;
|
||||
|
||||
.extra-text {
|
||||
cursor: pointer;
|
||||
color: @primary-color;
|
||||
}
|
||||
.extra-text {
|
||||
cursor: pointer;
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
.home-statistics-body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
.home-statistics-body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.step-item-after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -60px;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
transform: translateY(-50%);
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -60px;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
transform: translateY(-50%);
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
.home-step {
|
||||
.home-base;
|
||||
.home-base;
|
||||
|
||||
.home-step-items {
|
||||
display: grid;
|
||||
grid-column-gap: 66px;
|
||||
.home-step-items {
|
||||
display: grid;
|
||||
grid-column-gap: 66px;
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.step-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.step-item-title {
|
||||
position: relative;
|
||||
padding: 16px 24px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
background-color: #f8f9fd;
|
||||
cursor: pointer;
|
||||
.step-item-title {
|
||||
position: relative;
|
||||
padding: 16px 24px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
background-color: #f8f9fd;
|
||||
cursor: pointer;
|
||||
|
||||
.step-item-img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
height: 100%;
|
||||
.step-item-img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
}
|
||||
img {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
> span {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.step-item-content {
|
||||
flex-grow: 1;
|
||||
height: auto;
|
||||
padding: 24px;
|
||||
border-right: 1px solid #e5edf4;
|
||||
border-bottom: 1px solid #e5edf4;
|
||||
border-left: 1px solid #e5edf4;
|
||||
}
|
||||
}
|
||||
|
||||
> span {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.step-item-content {
|
||||
flex-grow: 1;
|
||||
height: auto;
|
||||
padding: 24px;
|
||||
border-right: 1px solid #e5edf4;
|
||||
border-bottom: 1px solid #e5edf4;
|
||||
border-left: 1px solid #e5edf4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.step-bar {
|
||||
position: relative;
|
||||
position: relative;
|
||||
|
||||
&.arrow-1 {
|
||||
&:not(:last-child) {
|
||||
&::after {
|
||||
.step-item-after;
|
||||
&.arrow-1 {
|
||||
&:not(:last-child) {
|
||||
&::after {
|
||||
.step-item-after;
|
||||
|
||||
background: url('/images/home/arrow-1.png') no-repeat center;
|
||||
}
|
||||
background: url('/images/home/arrow-1.png') no-repeat center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.arrow-2 {
|
||||
&:not(:last-child) {
|
||||
&::after {
|
||||
.step-item-after;
|
||||
&.arrow-2 {
|
||||
&:not(:last-child) {
|
||||
&::after {
|
||||
.step-item-after;
|
||||
|
||||
background: url('/images/home/arrow-2.png') no-repeat center;
|
||||
}
|
||||
background: url('/images/home/arrow-2.png') no-repeat center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
:request="queryList"
|
||||
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
|
||||
:params="params"
|
||||
:gridColumn="3"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
|
|
|
@ -115,6 +115,7 @@ const columns = [
|
|||
title: '支付URL',
|
||||
dataIndex: 'url',
|
||||
key: 'url',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '订单时间',
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<a-card>
|
||||
<h5>权限分配</h5>
|
||||
<PermissTree />
|
||||
<PermissTree v-model:select-items="form.menus" />
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
|
@ -39,12 +39,17 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts" name="RolePermiss">
|
||||
import { FormInstance } from 'ant-design-vue';
|
||||
import { FormInstance, message } from 'ant-design-vue';
|
||||
import PermissTree from '../components/PermissTree.vue';
|
||||
|
||||
import { getRoleDetails_api } from '@/api/system/role';
|
||||
import {
|
||||
getRoleDetails_api,
|
||||
updateRole_api,
|
||||
updatePrimissTree_api,
|
||||
} from '@/api/system/role';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const roleId = route.params.id as string;
|
||||
|
||||
// 表单相关
|
||||
|
@ -55,6 +60,7 @@ const form = reactive({
|
|||
name: '',
|
||||
description: '',
|
||||
},
|
||||
menus: [],
|
||||
getForm: () => {
|
||||
getRoleDetails_api(roleId).then((resp) => {
|
||||
if (resp.status) {
|
||||
|
@ -62,7 +68,15 @@ const form = reactive({
|
|||
}
|
||||
});
|
||||
},
|
||||
clickSave: () => {},
|
||||
clickSave: () => {
|
||||
const updateRole = updateRole_api(form.data);
|
||||
const updateTree = updatePrimissTree_api(roleId, { menu: form.menus });
|
||||
|
||||
Promise.all([updateRole, updateTree]).then((resp) => {
|
||||
message.success('操作成功');
|
||||
router.push('/system/Role');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
form.getForm();
|
||||
|
|
|
@ -1,64 +1,343 @@
|
|||
<template>
|
||||
<div class="permiss-tree-container">
|
||||
<a-table :data-source="dataSource">
|
||||
<a-table-column key="menu" data-index="menu">
|
||||
<template #title><span style="">菜单权限</span></template>
|
||||
</a-table-column>
|
||||
<a-table-column key="action" title="操作权限" data-index="action" />
|
||||
<a-table-column key="data" data-index="data">
|
||||
<template #title>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:pagination="false"
|
||||
:rowKey="'id'"
|
||||
>
|
||||
<!-- 自定义表头 -->
|
||||
<template #headerCell="{ column }">
|
||||
<div v-if="column.key === 'menu'">
|
||||
<a-checkbox
|
||||
v-model:checked="selectedAll"
|
||||
:indeterminate="indeterminate"
|
||||
@change="selectAllChange"
|
||||
>菜单权限</a-checkbox
|
||||
>
|
||||
</div>
|
||||
<div v-else-if="column.key === 'data'">
|
||||
<span style="">数据权限</span>
|
||||
<a-checkbox v-model:checked="checked">批量设置</a-checkbox>
|
||||
<a-checkbox
|
||||
v-model:checked="bulkShow"
|
||||
@change="bulkValue = ''"
|
||||
>批量设置</a-checkbox
|
||||
>
|
||||
<a-select
|
||||
v-show="checked"
|
||||
v-model:value="selectValue"
|
||||
v-show="bulkShow"
|
||||
v-model:value="bulkValue"
|
||||
:size="'middle'"
|
||||
style="width: 200px"
|
||||
:options="options"
|
||||
:options="bulkOptions"
|
||||
@change="bulkChange"
|
||||
></a-select>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span>{{ column.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 自定义表格内容 -->
|
||||
<template #bodyCell="{ column, record }">
|
||||
<div v-if="column.key === 'menu'">
|
||||
<a-checkbox
|
||||
v-model:checked="record.granted"
|
||||
:indeterminate="record.indeterminate"
|
||||
@change="menuChange(record)"
|
||||
>{{ record.name }}</a-checkbox
|
||||
>
|
||||
</div>
|
||||
|
||||
<div v-else-if="column.key === 'action'">
|
||||
<div v-if="record.buttons && record.buttons.length > 0">
|
||||
<a-checkbox
|
||||
v-for="button in record.buttons"
|
||||
v-model:checked="button.granted"
|
||||
@change="actionChange(record)"
|
||||
>{{ button.name }}</a-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="column.key === 'data'">
|
||||
<span v-if="record.accessSupport === undefined">
|
||||
不支持数据权限配置,默认可查看全部数据
|
||||
</span>
|
||||
<div v-else-if="record.accessSupport.value === 'support'">
|
||||
<a-radio-group v-model:value="record.selectAccesses">
|
||||
<a-radio
|
||||
:value="asset.supportId"
|
||||
v-for="asset in record.assetAccesses"
|
||||
>{{ asset.name }}</a-radio
|
||||
>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<span
|
||||
v-else-if="
|
||||
record.accessSupport.value === 'indirect' ||
|
||||
record.accessSupport.value === 'unsupported'
|
||||
"
|
||||
>{{ record.accessDescription }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { getPrimissTree_api } from '@/api/system/role';
|
||||
|
||||
const emits = defineEmits(['update:selectItems']);
|
||||
const route = useRoute();
|
||||
const props = defineProps({
|
||||
selectItems: Array,
|
||||
});
|
||||
const dataSource = ref([]);
|
||||
const checked = ref<boolean>(false);
|
||||
const options = [
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '菜单权限',
|
||||
dataIndex: 'menu',
|
||||
key: 'menu',
|
||||
width: '260px',
|
||||
},
|
||||
{
|
||||
title: '操作权限',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
width: '260px',
|
||||
},
|
||||
{
|
||||
title: '数据权限',
|
||||
dataIndex: 'data',
|
||||
key: 'data',
|
||||
width: '50%',
|
||||
},
|
||||
];
|
||||
const tableData = ref<tableItemType[]>([]);
|
||||
|
||||
// 表头-全选
|
||||
const selectedAll = ref<boolean>(false);
|
||||
const indeterminate = ref<boolean>(false);
|
||||
const selectAllChange = () => {
|
||||
flatTableData.forEach((item) => {
|
||||
item.granted = selectedAll.value;
|
||||
item.buttons?.forEach((button) => {
|
||||
button.granted = selectedAll.value;
|
||||
});
|
||||
});
|
||||
indeterminate.value = false;
|
||||
};
|
||||
// 表头-批量设置
|
||||
const bulkShow = ref<boolean>(false);
|
||||
const bulkOptions = [
|
||||
{
|
||||
label: '全部数据',
|
||||
value: '1',
|
||||
value: 'ignore',
|
||||
},
|
||||
{
|
||||
label: '所在组织及下级组织',
|
||||
value: '2',
|
||||
value: 'org-include-children',
|
||||
},
|
||||
{
|
||||
label: '所在组织',
|
||||
value: '3',
|
||||
value: 'org',
|
||||
},
|
||||
{
|
||||
label: '自己创建的',
|
||||
value: '4',
|
||||
value: 'creator',
|
||||
},
|
||||
];
|
||||
const selectValue = ref<string>('');
|
||||
|
||||
const getAllPermiss = () => {
|
||||
const id = route.params.id as string;
|
||||
getPrimissTree_api(id).then((resp) => {
|
||||
console.log(resp);
|
||||
const bulkValue = ref<string>('');
|
||||
const bulkChange = () => {
|
||||
if (!bulkValue) return;
|
||||
flatTableData.forEach((item) => {
|
||||
if (item.accessSupport && item.accessSupport.value === 'support') {
|
||||
item.selectAccesses = bulkValue.value;
|
||||
}
|
||||
});
|
||||
};
|
||||
// ------------下面为表格内容部分------------------
|
||||
const flatTableData: tableItemType[] = []; // 表格数据的扁平化版本--浅克隆 方便进行对表格数据进行操作
|
||||
|
||||
getAllPermiss();
|
||||
const init = () => {
|
||||
getAllPermiss();
|
||||
watch(tableData, () => {
|
||||
const selected = cloneDeep(flatTableData).filter((item) => (item.granted || item.indeterminate) && !item.parentId);
|
||||
selected.forEach((item) => {
|
||||
if (
|
||||
item.accessSupport &&
|
||||
item.accessSupport.value === 'support' &&
|
||||
item.selectAccesses
|
||||
) {
|
||||
item.selectAccesses = bulkValue.value;
|
||||
item.assetAccesses?.forEach((asset) => {
|
||||
if (asset.supportId === item.selectAccesses) {
|
||||
asset.granted = true;
|
||||
} else {
|
||||
asset.granted = false;
|
||||
}
|
||||
});
|
||||
delete item.selectAccesses;
|
||||
}
|
||||
delete item.indeterminate
|
||||
});
|
||||
emits(
|
||||
'update:selectItems',
|
||||
selected,
|
||||
);
|
||||
});
|
||||
};
|
||||
init();
|
||||
|
||||
function getAllPermiss() {
|
||||
const id = route.params.id as string;
|
||||
getPrimissTree_api(id).then((resp) => {
|
||||
tableData.value = resp.result;
|
||||
|
||||
treeToSimple(resp.result); // 表格数据扁平化
|
||||
|
||||
const selectList = flatTableData.filter((item) => item.granted); // 第一列选中的项
|
||||
emits('update:selectItems', selectList); // 选中的项传回父组件
|
||||
// 判断是全选/半全选
|
||||
if (selectList.length === flatTableData.length) {
|
||||
selectedAll.value = true;
|
||||
indeterminate.value = false;
|
||||
} else if (selectList.length > 0) {
|
||||
indeterminate.value = true;
|
||||
selectedAll.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 菜单权限改变事件
|
||||
* @param row 触发的项
|
||||
* @param setButtonBool 是否改变对应的操作权限
|
||||
*/
|
||||
function menuChange(
|
||||
row: tableItemType,
|
||||
setButtonBool: boolean = true,
|
||||
): undefined {
|
||||
if (setButtonBool) {
|
||||
if (row.buttons && row.buttons.length > 0)
|
||||
row.buttons.forEach((button) => {
|
||||
button.granted = row.granted;
|
||||
});
|
||||
row.children && setChildrenChecked(row.children, row.granted);
|
||||
}
|
||||
|
||||
// 改变上层节点的状态
|
||||
const selectList = flatTableData.filter((item) => item.granted); // 第一列选中的项
|
||||
if (row.parentId) {
|
||||
// 找到对应的父节点 判断该父节点的选中状态为 全选中/部分选中/未选中
|
||||
const parent = flatTableData.find(
|
||||
(item) => item.id === row.parentId,
|
||||
) as tableItemType;
|
||||
const selectLen = parent.children?.filter((item) => item.granted)
|
||||
.length as number; // 父节点的已选中子节点的数量
|
||||
|
||||
if (selectLen === parent.children?.length) {
|
||||
parent.granted = true;
|
||||
parent.indeterminate = false;
|
||||
} else if (selectLen > 0) {
|
||||
parent.granted = false;
|
||||
parent.indeterminate = true;
|
||||
} else {
|
||||
parent.granted = false;
|
||||
parent.indeterminate = false;
|
||||
}
|
||||
|
||||
if (parent.parentId) {
|
||||
return menuChange(parent, false);
|
||||
}
|
||||
}
|
||||
|
||||
// 改变头部节点状态
|
||||
if (selectList.length === flatTableData.length) {
|
||||
selectedAll.value = true;
|
||||
indeterminate.value = false;
|
||||
} else if (selectList.length > 0) {
|
||||
indeterminate.value = true;
|
||||
selectedAll.value = false;
|
||||
} else {
|
||||
selectedAll.value = false;
|
||||
indeterminate.value = false;
|
||||
}
|
||||
emits('update:selectItems', selectList); // 选中的项传回父组件
|
||||
}
|
||||
/**
|
||||
* 操作权限改变事件
|
||||
* @param row 触发的项
|
||||
*/
|
||||
function actionChange(row: tableItemType) {
|
||||
const selectLen = row.buttons?.filter((item) => item.granted)
|
||||
.length as number;
|
||||
|
||||
if (selectLen === row.buttons?.length) {
|
||||
row.granted = true;
|
||||
row.indeterminate = false;
|
||||
} else if (selectLen > 0) {
|
||||
row.granted = false;
|
||||
row.indeterminate = true;
|
||||
} else {
|
||||
row.granted = false;
|
||||
row.indeterminate = false;
|
||||
}
|
||||
|
||||
menuChange(row, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将树形结构的表格数据拍扁为一个普通数组
|
||||
* @param treeData
|
||||
*/
|
||||
function treeToSimple(treeData: tableItemType[]) {
|
||||
treeData.forEach((item) => {
|
||||
if (item.accessSupport && item.accessSupport.value === 'support') {
|
||||
const select =
|
||||
item.assetAccesses?.find((assetItem) => assetItem.granted) ||
|
||||
{};
|
||||
item.selectAccesses = select.supportId || '';
|
||||
}
|
||||
flatTableData.push(item);
|
||||
item.children && treeToSimple(item.children);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 设置子节点的状态
|
||||
* @param childrens
|
||||
* @param value
|
||||
*/
|
||||
function setChildrenChecked(childrens: tableItemType[], value: boolean) {
|
||||
if (childrens.length < 1) return;
|
||||
childrens.forEach((item) => {
|
||||
item.granted = value;
|
||||
if (item.buttons && item.buttons.length > 0)
|
||||
item.buttons.forEach((button) => {
|
||||
button.granted = value;
|
||||
});
|
||||
item.children && setChildrenChecked(item.children, value);
|
||||
});
|
||||
}
|
||||
|
||||
type buttonItemType = {
|
||||
supportId: string;
|
||||
name: string;
|
||||
granted: boolean;
|
||||
};
|
||||
type tableItemType = {
|
||||
id: string;
|
||||
granted: boolean;
|
||||
name: string;
|
||||
indeterminate?: boolean;
|
||||
parentId?: string;
|
||||
children?: tableItemType[];
|
||||
accessSupport?: any;
|
||||
buttons?: buttonItemType[];
|
||||
accessDescription?: string;
|
||||
selectAccesses?: string;
|
||||
assetAccesses?: any[];
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -15,4 +15,19 @@ const route = useRoute();
|
|||
const activeKey = ref('1');
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.details-container {
|
||||
|
||||
|
||||
|
||||
:deep(.ant-tabs-nav-wrap) {
|
||||
background-color: #fff;
|
||||
padding: 24px 0 0 24px;
|
||||
}
|
||||
|
||||
.role-permiss-container {
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue