Merge branch 'dev' into dev-hub
This commit is contained in:
commit
f7f0556b00
|
@ -0,0 +1,13 @@
|
||||||
|
import server from '@/utils/request'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// 列表
|
||||||
|
list: (data: any) => server.post(`/media/device/_query/`, data),
|
||||||
|
// 详情
|
||||||
|
detail: (id: string): any => server.get(`/media/device/${id}`),
|
||||||
|
// 新增
|
||||||
|
save: (data: any) => server.post(`/media/device/${data.channel}`, data),
|
||||||
|
// 修改
|
||||||
|
update: (data: any) => server.put(`/media/device/${data.channel}/${data.id}`, data),
|
||||||
|
del: (id: string) => server.remove(`/media/device/${id}`),
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import server from '@/utils/request';
|
||||||
|
/**
|
||||||
|
* 获取今日及当月告警数量
|
||||||
|
*/
|
||||||
|
export const dashboard = (data:Record<string,any[]>)=> server.post('/dashboard/_multi',data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const getAlarm = (params:Record<string,any[]>) => server.get('/alarm/record/_query',params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取告警数量
|
||||||
|
*/
|
||||||
|
export const getAlarmConfigCount = (data:Record<string,any>) => server.post('/alarm/config/_count',data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取报警等级
|
||||||
|
*/
|
||||||
|
export const getAlarmLevel = () => server.get('/alarm/config/default/level');
|
|
@ -10,12 +10,14 @@ export const updateDepartment_api = (data: object) => server.patch(`/organizatio
|
||||||
export const delDepartment_api = (id: string) => server.remove(`/organization/${id}`);
|
export const delDepartment_api = (id: string) => server.remove(`/organization/${id}`);
|
||||||
|
|
||||||
|
|
||||||
|
// 获取所属产品列表
|
||||||
|
export const getDeviceProduct_api = (data: object) => server.get(`/device/product/_query/no-paging`, data);
|
||||||
// 获取产品列表
|
// 获取产品列表
|
||||||
export const getDeviceOrProductList_api = (data: object) => server.post(`/device-product/_query`, data);
|
export const getDeviceOrProductList_api = (data: object) => server.post(`/device-product/_query`, data);
|
||||||
// 获取设备列表
|
// 获取设备列表
|
||||||
export const getDeviceList_api = (data: object) => server.post(`/device/instance/_query`, data);
|
export const getDeviceList_api = (data: object) => server.post(`/device/instance/_query`, data);
|
||||||
// 根据产品的id获取产品的权限
|
// 根据产品的id获取产品的权限
|
||||||
export const getPermission_api = (type:'device' | 'product',ids: object, id: string) => server.post(`/assets/bindings/${type}/org/${id}/_query`, ids);
|
export const getPermission_api = (type: 'device' | 'product', ids: object, id: string) => server.post(`/assets/bindings/${type}/org/${id}/_query`, ids);
|
||||||
// 获取产品的权限字典
|
// 获取产品的权限字典
|
||||||
export const getPermissionDict_api = () => server.get(`/assets/bindings/product/permissions`);
|
export const getPermissionDict_api = () => server.get(`/assets/bindings/product/permissions`);
|
||||||
|
|
||||||
|
@ -25,3 +27,12 @@ export const bindDeviceOrProductList_api = (type: 'device' | 'product', data: ob
|
||||||
export const unBindDeviceOrProduct_api = (type: 'device' | 'product', data: object) => server.post(`/assets/unbind/${type}`, data);
|
export const unBindDeviceOrProduct_api = (type: 'device' | 'product', data: object) => server.post(`/assets/unbind/${type}`, data);
|
||||||
// 批量更新权限
|
// 批量更新权限
|
||||||
export const updatePermission_api = (type: 'device' | 'product', parentId: string, data: object) => server.put(`/assets/permission/${type}/org/${parentId}/_batch`, data);
|
export const updatePermission_api = (type: 'device' | 'product', parentId: string, data: object) => server.put(`/assets/permission/${type}/org/${parentId}/_batch`, data);
|
||||||
|
|
||||||
|
|
||||||
|
// 用户相关
|
||||||
|
// 获取绑定用户列表
|
||||||
|
export const getBindUserList_api = (data: object) => server.post(`/user/_query`, data);
|
||||||
|
// 绑定用户
|
||||||
|
export const bindUser_api = (parentId:string,data: object) => server.post(`/organization/${parentId}/users/_bind`, data);
|
||||||
|
// 解绑用户
|
||||||
|
export const unBindUser_api = (parentId:string,data: object) => server.post(`/organization/${parentId}/users/_unbind`, data);
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import server from '@/utils/request';
|
||||||
|
|
||||||
|
// 获取用户类型
|
||||||
|
export const getUserType_api = () => server.get(`/user/detail/types`);
|
||||||
|
|
||||||
|
// 获取用户列表
|
||||||
|
export const getUserList_api = (data: object) => server.post(`/user/detail/_query`, data);
|
||||||
|
|
||||||
|
// 校验字段合法性
|
||||||
|
export const validateField_api = (type: 'username' | 'password', name: string) => server.post(`/user/${type}/_validate`, name, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取角色列表
|
||||||
|
export const getRoleList_api = () => server.get(`/role/_query/no-paging?paging=false`);
|
||||||
|
// 获取组织列表
|
||||||
|
export const getDepartmentList_api = () => server.get(`/organization/_all/tree?paging=false`);
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
export const getUser_api = (id: string) => server.get(`/user/detail/${id}`);
|
||||||
|
// 添加用户
|
||||||
|
export const addUser_api = (data: object) => server.post(`/user/detail/_create`, data);
|
||||||
|
// 更新用户
|
||||||
|
export const updateUser_api = (data: any) => server.put(`/user/detail/${data.id}/_update`, data);
|
||||||
|
// 更新密码
|
||||||
|
export const updatePassword_api = (data: { id: string, password: string }) => server.post(`/user/${data.id}/password/_reset`, data.password, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 修改用户状态
|
||||||
|
export const changeUserStatus_api = (data: object) => server.patch(`/user`,data);
|
||||||
|
// 删除用户
|
||||||
|
export const deleteUser_api = (id: string) => server.remove(`/user/${id}`);
|
|
@ -45,7 +45,10 @@ const iconKeys = [
|
||||||
'InfoCircleOutlined',
|
'InfoCircleOutlined',
|
||||||
'SearchOutlined',
|
'SearchOutlined',
|
||||||
'EllipsisOutlined',
|
'EllipsisOutlined',
|
||||||
'ClockCircleOutlined'
|
'ClockCircleOutlined',
|
||||||
|
'PartitionOutlined',
|
||||||
|
'ShareAltOutlined',
|
||||||
|
'playCircleOutlined',
|
||||||
]
|
]
|
||||||
|
|
||||||
const Icon = (props: {type: string}) => {
|
const Icon = (props: {type: string}) => {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
|
||||||
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
|
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
|
||||||
|
|
||||||
self.MonacoEnvironment = {
|
self.MonacoEnvironment = {
|
||||||
getWorker(workerId, label) {
|
getWorker(_, label) {
|
||||||
if (label === 'json') {
|
if (label === 'json') {
|
||||||
return new jsonWorker();
|
return new jsonWorker();
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ self.MonacoEnvironment = {
|
||||||
if (label === 'html') {
|
if (label === 'html') {
|
||||||
return new htmlWorker();
|
return new htmlWorker();
|
||||||
}
|
}
|
||||||
if (label === 'ts') {
|
if (['typescript', 'javascript'].includes(label)) {
|
||||||
return new tsWorker();
|
return new tsWorker();
|
||||||
}
|
}
|
||||||
return new editorWorker();
|
return new editorWorker();
|
||||||
|
@ -33,6 +33,7 @@ self.MonacoEnvironment = {
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: [String, Number],
|
modelValue: [String, Number],
|
||||||
theme: { type: String, default: 'vs-dark' },
|
theme: { type: String, default: 'vs-dark' },
|
||||||
|
language: { type: String, default: 'json' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
@ -42,10 +43,10 @@ const dom = ref();
|
||||||
let instance;
|
let instance;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const jsonModel = monaco.editor.createModel(props.modelValue, 'json');
|
const _model = monaco.editor.createModel(props.modelValue, props.language);
|
||||||
|
|
||||||
instance = monaco.editor.create(dom.value, {
|
instance = monaco.editor.create(dom.value, {
|
||||||
model: jsonModel,
|
model: _model,
|
||||||
tabSize: 2,
|
tabSize: 2,
|
||||||
automaticLayout: true,
|
automaticLayout: true,
|
||||||
scrollBeyondLastLine: false,
|
scrollBeyondLastLine: false,
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<template v-if="isPermission">
|
<template v-if="isPermission">
|
||||||
<template v-if="popConfirm">
|
<template v-if="popConfirm">
|
||||||
<a-popconfirm v-bind="popConfirm" @confirm="conform" :disabled="!isPermission || props.disabled">
|
<a-popconfirm v-bind="popConfirm" :disabled="!isPermission || props.disabled">
|
||||||
<a-tooltip v-if="tooltip" v-bind="tooltip">
|
<a-tooltip v-if="tooltip" v-bind="tooltip">
|
||||||
<slot v-if="noButton"></slot>
|
<slot v-if="noButton"></slot>
|
||||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" @click="handleClick">
|
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" >
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<slot name="icon"></slot>
|
<slot name="icon"></slot>
|
||||||
</template>
|
</template>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" @click="handleClick">
|
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" >
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<slot name="icon"></slot>
|
<slot name="icon"></slot>
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
<template v-else-if="tooltip">
|
<template v-else-if="tooltip">
|
||||||
<a-tooltip v-bind="tooltip">
|
<a-tooltip v-bind="tooltip">
|
||||||
<slot v-if="noButton"></slot>
|
<slot v-if="noButton"></slot>
|
||||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" @click="handleClick">
|
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" >
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<slot name="icon"></slot>
|
<slot name="icon"></slot>
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<slot v-if="noButton"></slot>
|
<slot v-if="noButton"></slot>
|
||||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" @click="handleClick">
|
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" >
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<slot name="icon"></slot>
|
<slot name="icon"></slot>
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
</template>
|
</template>
|
||||||
<a-tooltip v-else title="没有权限">
|
<a-tooltip v-else title="没有权限">
|
||||||
<slot v-if="noButton"></slot>
|
<slot v-if="noButton"></slot>
|
||||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" @click="handleClick">
|
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" >
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<slot name="icon"></slot>
|
<slot name="icon"></slot>
|
||||||
|
@ -56,11 +56,11 @@ import { TooltipProps, PopconfirmProps } from 'ant-design-vue/es'
|
||||||
import { buttonProps } from 'ant-design-vue/es/button/button'
|
import { buttonProps } from 'ant-design-vue/es/button/button'
|
||||||
import { usePermissionStore } from '@/store/permission';
|
import { usePermissionStore } from '@/store/permission';
|
||||||
|
|
||||||
interface PermissionButtonEmits {
|
// interface PermissionButtonEmits {
|
||||||
(e: 'click', data: MouseEvent): void;
|
// (e: 'click', data: MouseEvent): void;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const emits = defineEmits<PermissionButtonEmits>()
|
// const emits = defineEmits<PermissionButtonEmits>()
|
||||||
|
|
||||||
// interface PermissionButtonProps extends ButtonProps {
|
// interface PermissionButtonProps extends ButtonProps {
|
||||||
// tooltip?: TooltipProps;
|
// tooltip?: TooltipProps;
|
||||||
|
@ -100,18 +100,15 @@ const isPermission = computed(() => {
|
||||||
})
|
})
|
||||||
const _isPermission = computed(() =>
|
const _isPermission = computed(() =>
|
||||||
'hasPermission' in props && isPermission.value
|
'hasPermission' in props && isPermission.value
|
||||||
? 'disabled' in buttonProps
|
? 'disabled' in _buttonProps
|
||||||
? buttonProps.disabled as boolean
|
? _buttonProps.disabled as boolean
|
||||||
: false
|
: false
|
||||||
: true
|
: true
|
||||||
)
|
)
|
||||||
const handleClick = (e: MouseEvent) => {
|
|
||||||
emits('click', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
const conform = (e: MouseEvent) => {
|
// const conform = (e: MouseEvent) => {
|
||||||
props.popConfirm?.onConfirm?.(e)
|
// props.popConfirm?.onConfirm?.(e)
|
||||||
}
|
// }
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
:key="index"
|
:key="index"
|
||||||
@click="myValue = item.value"
|
@click="myValue = item.value"
|
||||||
>
|
>
|
||||||
<img class="img" :src="item.logo" alt="" />
|
<img v-if="item.logo" class="img" :src="item.logo" alt="" />
|
||||||
<span>{{ item.label }}</span>
|
<span>{{ item.label }}</span>
|
||||||
<div
|
<div
|
||||||
:class="[
|
:class="[
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
<Charts :options="TodayDevOptions"></Charts> </TopCard
|
<Charts :options="TodayDevOptions"></Charts> </TopCard
|
||||||
></a-col>
|
></a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row :span="24">
|
<a-row :gutter="24">
|
||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<div class="message-card">
|
<div class="message-card">
|
||||||
<Guide title="设备消息">
|
<Guide title="设备消息">
|
||||||
|
@ -452,6 +452,7 @@ const getEcharts = (data: any) => {
|
||||||
_time = '1M';
|
_time = '1M';
|
||||||
format = 'yyyy年-M月';
|
format = 'yyyy年-M月';
|
||||||
}
|
}
|
||||||
|
|
||||||
dashboard([
|
dashboard([
|
||||||
{
|
{
|
||||||
dashboard: 'device',
|
dashboard: 'device',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<div class="wrapper">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="title">{{ title }}</div>
|
<div class="title">{{ title }}</div>
|
||||||
<div class="tools">
|
<div class="tools">
|
||||||
|
@ -13,18 +13,28 @@
|
||||||
<a-radio-button value="month">近一月</a-radio-button>
|
<a-radio-button value="month">近一月</a-radio-button>
|
||||||
<a-radio-button value="year">近一年</a-radio-button>
|
<a-radio-button value="year">近一年</a-radio-button>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
<a-range-picker v-model:value="dateRange" />
|
<a-range-picker
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
valueFormat="x"
|
||||||
|
v-model:value="dateRange"
|
||||||
|
/>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart" ref="chartRef"></div>
|
<div v-if="chartData.length" class="chart" ref="chartRef"></div>
|
||||||
|
<a-empty v-else class="no-data" description="暂无数据"></a-empty>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
// const { proxy } = <any>getCurrentInstance();
|
// const { proxy } = <any>getCurrentInstance();
|
||||||
|
type Emits = {
|
||||||
|
(e: 'change', data: any): void;
|
||||||
|
};
|
||||||
|
const emits = defineEmits<Emits>();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: { type: String, default: '' },
|
title: { type: String, default: '' },
|
||||||
|
@ -34,7 +44,10 @@ const props = defineProps({
|
||||||
|
|
||||||
// 统计时间维度
|
// 统计时间维度
|
||||||
const dimension = ref('week');
|
const dimension = ref('week');
|
||||||
const dateRange = ref<any>([]);
|
const dateRange = ref<any>([
|
||||||
|
moment().subtract(1, 'week').format('x'),
|
||||||
|
moment().format('x'),
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 绘制图表
|
* 绘制图表
|
||||||
|
@ -92,6 +105,7 @@ const createChart = () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
myChart.setOption(options);
|
myChart.setOption(options);
|
||||||
window.addEventListener('resize', function () {
|
window.addEventListener('resize', function () {
|
||||||
myChart.resize();
|
myChart.resize();
|
||||||
|
@ -101,24 +115,73 @@ const createChart = () => {
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.chartData,
|
() => props.chartData,
|
||||||
() => createChart(),
|
(val) => {
|
||||||
|
console.log('createChart', val);
|
||||||
|
|
||||||
|
createChart();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
() => dateRange.value,
|
||||||
|
(val) => {
|
||||||
|
emits('change', {
|
||||||
|
time: {
|
||||||
|
start: val[0],
|
||||||
|
end: val[1],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
{ immediate: true, deep: true },
|
{ immediate: true, deep: true },
|
||||||
);
|
);
|
||||||
|
watch(
|
||||||
|
() => dimension.value,
|
||||||
|
(val) => {
|
||||||
|
if (val === 'today') {
|
||||||
|
dateRange[0] = moment().startOf('day').format('x');
|
||||||
|
}
|
||||||
|
if (val === 'week') {
|
||||||
|
dateRange[0] = moment().subtract(1, 'week').format('x');
|
||||||
|
}
|
||||||
|
if (val === 'month') {
|
||||||
|
dateRange[0] = moment().subtract(1, 'month').format('x');
|
||||||
|
}
|
||||||
|
if (val === 'year') {
|
||||||
|
dateRange[0] = moment().subtract(1, 'year').format('x');
|
||||||
|
}
|
||||||
|
dateRange[1] = moment().format('x');
|
||||||
|
emits('change', {
|
||||||
|
time: {
|
||||||
|
start: dateRange[0],
|
||||||
|
end: dateRange[1],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.page-container {
|
.wrapper {
|
||||||
|
padding: 24px;
|
||||||
|
background-color: #fff;
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
margin-bottom: 24px;
|
||||||
.title {
|
.title {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.chart {
|
.chart,
|
||||||
|
.no-data {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
min-height: calc(100vh - 430px);
|
||||||
|
}
|
||||||
|
.no-data {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -34,8 +34,12 @@
|
||||||
:value="aggPlayingTotal"
|
:value="aggPlayingTotal"
|
||||||
/>
|
/>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="24">
|
<a-col :span="24" class="dash-board-bottom">
|
||||||
<Card title="播放数量(人次)" :chartData="chartData" />
|
<Card
|
||||||
|
title="播放数量(人次)"
|
||||||
|
:chartData="chartData"
|
||||||
|
@change="getPlayCount"
|
||||||
|
/>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,6 +54,7 @@ import dashboardApi from '@/api/media/dashboard';
|
||||||
import type { Footer } from '@/views/media/DashBoard/typings';
|
import type { Footer } from '@/views/media/DashBoard/typings';
|
||||||
import encodeQuery from '@/utils/encodeQuery';
|
import encodeQuery from '@/utils/encodeQuery';
|
||||||
import { timestampFormat } from '@/utils/utils';
|
import { timestampFormat } from '@/utils/utils';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
// 设备
|
// 设备
|
||||||
const deviceFooter = ref<Footer[]>([]);
|
const deviceFooter = ref<Footer[]>([]);
|
||||||
|
@ -114,10 +119,12 @@ const aggTotal = ref(0);
|
||||||
const getAggData = () => {
|
const getAggData = () => {
|
||||||
dashboardApi.agg().then((res) => {
|
dashboardApi.agg().then((res) => {
|
||||||
aggTotal.value = res.result.total;
|
aggTotal.value = res.result.total;
|
||||||
aggFooter.value.push({
|
aggFooter.value = [
|
||||||
title: '总时长',
|
{
|
||||||
value: timestampFormat(res.result.duration),
|
title: '总时长',
|
||||||
});
|
value: timestampFormat(res.result.duration),
|
||||||
|
},
|
||||||
|
];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
getAggData();
|
getAggData();
|
||||||
|
@ -128,10 +135,12 @@ const aggPlayingTotal = ref(0);
|
||||||
const getAggPlayingData = () => {
|
const getAggPlayingData = () => {
|
||||||
dashboardApi.aggPlaying().then((res) => {
|
dashboardApi.aggPlaying().then((res) => {
|
||||||
aggTotal.value = res.result.playingTotal;
|
aggTotal.value = res.result.playingTotal;
|
||||||
aggPlayingFooter.value.push({
|
aggPlayingFooter.value = [
|
||||||
title: '播放人数',
|
{
|
||||||
value: res.result.playerTotal,
|
title: '播放人数',
|
||||||
});
|
value: res.result.playerTotal,
|
||||||
|
},
|
||||||
|
];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
getAggPlayingData();
|
getAggPlayingData();
|
||||||
|
@ -140,24 +149,61 @@ getAggPlayingData();
|
||||||
* 获取播放数量(人次)
|
* 获取播放数量(人次)
|
||||||
*/
|
*/
|
||||||
const chartData = ref([]);
|
const chartData = ref([]);
|
||||||
const getPlayCount = async () => {
|
const getPlayCount = async (params: any) => {
|
||||||
const params = {};
|
let _time = '1h';
|
||||||
dashboardApi.getPlayCount(params).then((res) => {
|
let _limit = 12;
|
||||||
let result: any = [];
|
const dt = params.time.end - params.time.start;
|
||||||
res.result.forEach((item: any) => {
|
const hour = 60 * 60 * 1000;
|
||||||
result = [...result, ...item.data];
|
const day = hour * 24;
|
||||||
|
const month = day * 30;
|
||||||
|
const year = 365 * day;
|
||||||
|
if (dt <= day) {
|
||||||
|
_limit = Math.abs(Math.ceil(dt / hour));
|
||||||
|
} else if (dt > day && dt < year) {
|
||||||
|
_limit = Math.abs(Math.ceil(dt / day));
|
||||||
|
_time = '1d';
|
||||||
|
} else if (dt >= year) {
|
||||||
|
_limit = Math.abs(Math.floor(dt / month));
|
||||||
|
_time = '1M';
|
||||||
|
}
|
||||||
|
dashboardApi
|
||||||
|
.getPlayCount([
|
||||||
|
{
|
||||||
|
dashboard: 'media_stream',
|
||||||
|
object: 'play_count',
|
||||||
|
measurement: 'quantity',
|
||||||
|
dimension: 'agg',
|
||||||
|
group: 'playCount',
|
||||||
|
params: {
|
||||||
|
time: _time,
|
||||||
|
from: moment(Number(params.time.start)).format(
|
||||||
|
'YYYY-MM-DD HH:mm:ss',
|
||||||
|
),
|
||||||
|
to: moment(Number(params.time.end)).format(
|
||||||
|
'YYYY-MM-DD HH:mm:ss',
|
||||||
|
),
|
||||||
|
limit: _limit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.then((res) => {
|
||||||
|
let result: any = [];
|
||||||
|
res.result.forEach((item: any) => {
|
||||||
|
result = [...result, ...item.data];
|
||||||
|
});
|
||||||
|
chartData.value = result.map((m: any) => ({
|
||||||
|
x: m.timeString,
|
||||||
|
value: m.value,
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
chartData.value = result.map((m: any) => ({
|
|
||||||
x: m.timeString,
|
|
||||||
value: m.value,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
getPlayCount();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.page-container {
|
.page-container {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
|
.dash-board-bottom {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -11,6 +11,6 @@ export type AggPlaying = {
|
||||||
export type Footer = {
|
export type Footer = {
|
||||||
title: string;
|
title: string;
|
||||||
value: number | string;
|
value: number | string;
|
||||||
status?: "default" | "error" | "success" | "warning" | "processing"
|
status?: "default" | "error" | "success" | "warning" | "processing" | ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
.doc {
|
||||||
|
height: 1050px;
|
||||||
|
padding: 24px;
|
||||||
|
overflow-y: auto;
|
||||||
|
color: rgba(#000, 0.8);
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
|
||||||
|
.url {
|
||||||
|
padding: 8px 16px;
|
||||||
|
color: #2f54eb;
|
||||||
|
background-color: rgba(#a7bdf7, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 16px 0;
|
||||||
|
color: rgba(#000, 0.85);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 6px 0;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,429 @@
|
||||||
|
<!-- 通知模板详情 -->
|
||||||
|
<template>
|
||||||
|
<div class="page-container">
|
||||||
|
<a-card>
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form layout="vertical">
|
||||||
|
<a-form-item
|
||||||
|
label="接入方式"
|
||||||
|
v-bind="validateInfos.channel"
|
||||||
|
>
|
||||||
|
<RadioCard
|
||||||
|
layout="horizontal"
|
||||||
|
:options="PROVIDER_OPTIONS"
|
||||||
|
:checkStyle="true"
|
||||||
|
:disabled="!!formData.id"
|
||||||
|
v-model="formData.channel"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="8">
|
||||||
|
<!-- <div class="upload-image-warp-logo">
|
||||||
|
<div class="upload-image-border-logo">
|
||||||
|
<a-upload
|
||||||
|
name="file"
|
||||||
|
:action="FILE_UPLOAD"
|
||||||
|
:headers="{
|
||||||
|
[TOKEN_KEY]:
|
||||||
|
LocalStore.get(TOKEN_KEY),
|
||||||
|
}"
|
||||||
|
:showUploadList="false"
|
||||||
|
accept="image/jpeg', 'image/png"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="upload-image-content-logo"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="loading-logo"
|
||||||
|
v-if="form.logoLoading"
|
||||||
|
>
|
||||||
|
<LoadingOutlined
|
||||||
|
style="font-size: 28px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="upload-image"
|
||||||
|
style="height: 100%"
|
||||||
|
v-if="formValue.logo"
|
||||||
|
:style="
|
||||||
|
formValue.logo
|
||||||
|
? `background-image: url(${formValue.logo});`
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
v-if="formValue.logo"
|
||||||
|
class="upload-image-mask"
|
||||||
|
>
|
||||||
|
点击修改
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div
|
||||||
|
v-if="form.logoLoading"
|
||||||
|
>
|
||||||
|
<LoadingOutlined
|
||||||
|
style="
|
||||||
|
font-size: 28px;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<PlusOutlined
|
||||||
|
style="
|
||||||
|
font-size: 28px;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-upload>
|
||||||
|
<div v-if="form.logoLoading">
|
||||||
|
<div class="upload-loading-mask">
|
||||||
|
<LoadingOutlined
|
||||||
|
style="font-size: 28px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item
|
||||||
|
label="ID"
|
||||||
|
v-bind="validateInfos.id"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="formData.id"
|
||||||
|
placeholder="请输入"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
label="设备名称"
|
||||||
|
v-bind="validateInfos.name"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="formData.name"
|
||||||
|
placeholder="请输入名称"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-form-item
|
||||||
|
label="所属产品"
|
||||||
|
v-bind="validateInfos.productId"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<a-select
|
||||||
|
v-model:value="formData.productId"
|
||||||
|
placeholder="请选择所属产品"
|
||||||
|
>
|
||||||
|
<!-- <a-select-option
|
||||||
|
v-for="(item, index) in NOTICE_METHOD"
|
||||||
|
:key="index"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</a-select-option> -->
|
||||||
|
</a-select>
|
||||||
|
<AIcon type="PlusCircleOutlined" />
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
label="接入密码"
|
||||||
|
v-bind="validateInfos['others.access_pwd']"
|
||||||
|
>
|
||||||
|
<a-input-password
|
||||||
|
v-model:value="formData.others.access_pwd"
|
||||||
|
placeholder="请输入接入密码"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="说明">
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="formData.description"
|
||||||
|
show-count
|
||||||
|
:maxlength="200"
|
||||||
|
:rows="5"
|
||||||
|
placeholder="请输入说明"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :wrapper-col="{ offset: 0, span: 3 }">
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
@click="handleSubmit"
|
||||||
|
:loading="btnLoading"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<div v-if="1" class="doc" style="height: 800">
|
||||||
|
<h1>1.概述</h1>
|
||||||
|
<div>
|
||||||
|
视频设备通过GB/T28181接入平台整体分为2部分,包括平台端配置和设备端配置,不同的设备端配置的路径或页面存在差异,但配置项基本大同小异。
|
||||||
|
</div>
|
||||||
|
<h1>2.配置说明</h1>
|
||||||
|
<h1>平台端配置</h1>
|
||||||
|
<h2>1、ID</h2>
|
||||||
|
<div>设备唯一标识,请填写设备端配置的设备编号。</div>
|
||||||
|
<h2>2、所属产品</h2>
|
||||||
|
<div>
|
||||||
|
只能选择接入方式为GB/T28281的产品,若当前无对应产品,可点击右侧快速添加按钮,填写产品名称和选择GB/T28181类型的网关完成产品创建
|
||||||
|
</div>
|
||||||
|
<h2>3、接入密码</h2>
|
||||||
|
<div>
|
||||||
|
配置接入密码,设备端配置的密码需与该密码一致。该字段可在产品-设备接入页面进行统一配置,配置后所有设备将继承产品配置。设备单独修改后将脱离继承关系。
|
||||||
|
</div>
|
||||||
|
<h1>设备端配置</h1>
|
||||||
|
<div>
|
||||||
|
各个厂家、不同设备型号的设备端配置页面布局存在差异,但配置项基本大同小异,此处以大华摄像头为例作为接入配置示例
|
||||||
|
</div>
|
||||||
|
<div class="image">
|
||||||
|
<a-image
|
||||||
|
width="100%"
|
||||||
|
:src="getImage('/media/doc1.png')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h2>1、SIP服务器编号/SIP域</h2>
|
||||||
|
<div>
|
||||||
|
SIP服务器编号填入该设备所属产品-接入方式页面“连接信息”的SIP。
|
||||||
|
SIP域通常为SIP服务器编号的前10位。
|
||||||
|
</div>
|
||||||
|
<div class="image">
|
||||||
|
<a-image
|
||||||
|
width="100%"
|
||||||
|
:src="getImage('/media/doc2.png')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h2>2、SIP服务器IP/端口</h2>
|
||||||
|
<div>
|
||||||
|
SIP服务器IP/端口填入该设备所属产品-接入方式页面中“连接信息”的IP/端口。
|
||||||
|
</div>
|
||||||
|
<div class="image">
|
||||||
|
<a-image
|
||||||
|
width="100%"
|
||||||
|
:src="getImage('/media/doc3.png')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h2>3、设备编号</h2>
|
||||||
|
<div>
|
||||||
|
设备编号为设备唯一性标识,物联网平台的设备接入没有校验该字段,输入任意数字均不影响设备接入平台。
|
||||||
|
</div>
|
||||||
|
<h2>4、注册密码</h2>
|
||||||
|
<div>
|
||||||
|
填入该设备所属产品-接入方式页面中“GB28281配置”处的接入密码
|
||||||
|
</div>
|
||||||
|
<div class="image">
|
||||||
|
<a-image
|
||||||
|
width="100%"
|
||||||
|
:src="getImage('/media/doc4.png')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h2>5、其他字段</h2>
|
||||||
|
<div>不影响设备接入平台,可保持设备初始化值。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="doc" style="height: 600">
|
||||||
|
<h1>1.概述</h1>
|
||||||
|
<div>
|
||||||
|
视频设备通过RTSP、RTMP固定地址接入平台分为2步。
|
||||||
|
</div>
|
||||||
|
<div>1、添加视频设备</div>
|
||||||
|
<div>2、添加视频下的通道地址。</div>
|
||||||
|
<div>
|
||||||
|
注:当前页面为新增视频设备,新增完成后点击设备的“通道”按钮,添加通道。
|
||||||
|
</div>
|
||||||
|
<h1>2.配置说明</h1>
|
||||||
|
<h2>1、ID</h2>
|
||||||
|
<div>
|
||||||
|
设备唯一标识,若不填写,系统将自动生成唯一标识。
|
||||||
|
</div>
|
||||||
|
<h2>2、所属产品</h2>
|
||||||
|
<div>
|
||||||
|
只能选择接入方式为固定地址的产品,若当前无对应产品,可点击右侧快速添加按钮,填写产品名称和选择固定地址类型的网关完成产品创建。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { getImage } from '@/utils/comm';
|
||||||
|
import { Form } from 'ant-design-vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import templateApi from '@/api/notice/template';
|
||||||
|
|
||||||
|
import { FILE_UPLOAD } from '@/api/comm';
|
||||||
|
import { LocalStore } from '@/utils/comm';
|
||||||
|
import { TOKEN_KEY } from '@/utils/variable';
|
||||||
|
import { PROVIDER_OPTIONS } from '@/views/media/Device/const';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
const useForm = Form.useForm;
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const formData = ref({
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
channel: 'gb28181-2016',
|
||||||
|
photoUrl: '',
|
||||||
|
productId: '',
|
||||||
|
others: {
|
||||||
|
access_pwd: '',
|
||||||
|
},
|
||||||
|
description: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 验证规则
|
||||||
|
const formRules = ref({
|
||||||
|
id: [
|
||||||
|
{ required: true, message: '请输入ID' },
|
||||||
|
{ max: 64, message: '最多输入64个字符' },
|
||||||
|
{
|
||||||
|
pattern: /^[a-zA-Z0-9_\-]+$/,
|
||||||
|
message: '请输入英文或者数字或者-或者_',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: [
|
||||||
|
{ required: true, message: '请输入名称' },
|
||||||
|
{ max: 64, message: '最多可输入64个字符' },
|
||||||
|
],
|
||||||
|
productId: [{ required: true, message: '请选择所属产品' }],
|
||||||
|
channel: [{ required: true, message: '请选择接入方式' }],
|
||||||
|
'others.access_pwd': [{ required: true, message: '请输入接入密码' }],
|
||||||
|
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { resetFields, validate, validateInfos, clearValidate } = useForm(
|
||||||
|
formData.value,
|
||||||
|
formRules.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
const clearValid = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
formData.value.variableDefinitions = [];
|
||||||
|
clearValidate();
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取详情
|
||||||
|
*/
|
||||||
|
const getDetail = async () => {
|
||||||
|
const res = await templateApi.detail(route.params.id as string);
|
||||||
|
// console.log('res: ', res);
|
||||||
|
formData.value = res.result;
|
||||||
|
// console.log('formData.value: ', formData.value);
|
||||||
|
};
|
||||||
|
// getDetail();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单提交
|
||||||
|
*/
|
||||||
|
const btnLoading = ref<boolean>(false);
|
||||||
|
const handleSubmit = () => {
|
||||||
|
// console.log('formData.value: ', formData.value);
|
||||||
|
validate()
|
||||||
|
.then(async () => {
|
||||||
|
btnLoading.value = true;
|
||||||
|
let res;
|
||||||
|
if (!formData.value.id) {
|
||||||
|
res = await templateApi.save(formData.value);
|
||||||
|
} else {
|
||||||
|
res = await templateApi.update(formData.value);
|
||||||
|
}
|
||||||
|
// console.log('res: ', res);
|
||||||
|
if (res?.success) {
|
||||||
|
message.success('保存成功');
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('err: ', err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
btnLoading.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
@import './index.less';
|
||||||
|
.page-container {
|
||||||
|
background: #f0f2f5;
|
||||||
|
padding: 24px;
|
||||||
|
.upload-image-warp-logo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
.upload-image-border-logo {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px dashed #d9d9d9;
|
||||||
|
transition: all 0.3s;
|
||||||
|
width: 160px;
|
||||||
|
height: 150px;
|
||||||
|
&:hover {
|
||||||
|
border: 1px dashed #1890ff;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.upload-image-content-logo {
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 160px;
|
||||||
|
height: 150px;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.06);
|
||||||
|
cursor: pointer;
|
||||||
|
.loading-logo {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
.loading-icon {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.upload-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 50%;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
.upload-image-icon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 50%;
|
||||||
|
background-size: inherit;
|
||||||
|
}
|
||||||
|
.upload-image-mask {
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
&:hover .upload-image-mask {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,4 @@
|
||||||
|
export const PROVIDER_OPTIONS = [
|
||||||
|
{ label: '固定地址', value: 'fixed-media' },
|
||||||
|
{ label: 'GB/T28181', value: 'gb28181-2016' },
|
||||||
|
]
|
|
@ -0,0 +1,332 @@
|
||||||
|
<template>
|
||||||
|
<div class="page-container">
|
||||||
|
<Search
|
||||||
|
:columns="columns"
|
||||||
|
target="notice-config"
|
||||||
|
@search="handleSearch"
|
||||||
|
/>
|
||||||
|
<JTable
|
||||||
|
ref="listRef"
|
||||||
|
:columns="columns"
|
||||||
|
:request="DeviceApi.list"
|
||||||
|
:defaultParams="{
|
||||||
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
|
}"
|
||||||
|
:params="params"
|
||||||
|
>
|
||||||
|
<template #headerTitle>
|
||||||
|
<a-button type="primary" @click="handleAdd"> 新增 </a-button>
|
||||||
|
</template>
|
||||||
|
<template #card="slotProps">
|
||||||
|
<CardBox
|
||||||
|
:value="slotProps"
|
||||||
|
:actions="getActions(slotProps, 'card')"
|
||||||
|
v-bind="slotProps"
|
||||||
|
:showStatus="true"
|
||||||
|
:status="
|
||||||
|
slotProps.state.value === 'online' ? 'success' : 'error'
|
||||||
|
"
|
||||||
|
:statusText="slotProps.state.text"
|
||||||
|
:statusNames="{ success: 'success', error: 'error' }"
|
||||||
|
>
|
||||||
|
<template #img>
|
||||||
|
<slot name="img">
|
||||||
|
<img :src="getImage('/device-media.png')" />
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<h3 class="card-item-content-title">
|
||||||
|
{{ slotProps.name }}
|
||||||
|
</h3>
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="12">
|
||||||
|
<div class="card-item-content-text">厂商</div>
|
||||||
|
<div>{{ slotProps.manufacturer }}</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<div class="card-item-content-text">
|
||||||
|
通道数量
|
||||||
|
</div>
|
||||||
|
<div>{{ slotProps.channelNumber }}</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<div class="card-item-content-text">型号</div>
|
||||||
|
<div>{{ slotProps.model }}</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<div class="card-item-content-text">
|
||||||
|
接入方式
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ providerType[slotProps.provider] }}
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #actions="item">
|
||||||
|
<a-tooltip
|
||||||
|
v-bind="item.tooltip"
|
||||||
|
:title="item.disabled && item.tooltip.title"
|
||||||
|
>
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="item.popConfirm"
|
||||||
|
v-bind="item.popConfirm"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
>
|
||||||
|
<a-button :disabled="item.disabled">
|
||||||
|
<AIcon type="DeleteOutlined" />
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<template v-else>
|
||||||
|
<a-button
|
||||||
|
:disabled="item.disabled"
|
||||||
|
@click="item.onClick"
|
||||||
|
>
|
||||||
|
<AIcon :type="item.icon" />
|
||||||
|
<span>{{ item.text }}</span>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</CardBox>
|
||||||
|
</template>
|
||||||
|
<template #action="slotProps">
|
||||||
|
<a-space :size="16">
|
||||||
|
<a-tooltip
|
||||||
|
v-for="i in getActions(slotProps, 'table')"
|
||||||
|
:key="i.key"
|
||||||
|
v-bind="i.tooltip"
|
||||||
|
>
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="i.popConfirm"
|
||||||
|
v-bind="i.popConfirm"
|
||||||
|
:disabled="i.disabled"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import DeviceApi from '@/api/media/device';
|
||||||
|
import type { ActionsType } from '@/components/Table/index.vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { getImage } from '@/utils/comm';
|
||||||
|
import { PROVIDER_OPTIONS } from '@/views/media/Device/const';
|
||||||
|
|
||||||
|
const providerType = {
|
||||||
|
'gb28181-2016': 'GB/T28181',
|
||||||
|
'fixed-media': '固定地址',
|
||||||
|
};
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const listRef = ref<Record<string, any>>({});
|
||||||
|
const params = ref<Record<string, any>>({});
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'ID',
|
||||||
|
dataIndex: 'id',
|
||||||
|
key: 'id',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '接入方式',
|
||||||
|
dataIndex: 'type',
|
||||||
|
key: 'type',
|
||||||
|
scopedSlots: true,
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: PROVIDER_OPTIONS,
|
||||||
|
handleValue: (v: any) => {
|
||||||
|
return '123';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '通道数量',
|
||||||
|
dataIndex: 'channelNumber',
|
||||||
|
key: 'channelNumber',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '厂商',
|
||||||
|
dataIndex: 'manufacturer',
|
||||||
|
key: 'manufacturer',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '产品名称',
|
||||||
|
dataIndex: 'productId',
|
||||||
|
key: 'productId',
|
||||||
|
scopedSlots: true,
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{ label: '固定地址', value: 'fixed-media' },
|
||||||
|
{ label: 'GB/T28181', value: 'gb28181-2016' },
|
||||||
|
],
|
||||||
|
handleValue: (v: any) => {
|
||||||
|
return '123';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'state',
|
||||||
|
key: 'state',
|
||||||
|
scopedSlots: true,
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{ label: '禁用', value: 'notActive' },
|
||||||
|
{ label: '离线', value: 'offline' },
|
||||||
|
{ label: '在线', value: 'online' },
|
||||||
|
],
|
||||||
|
handleValue: (v: any) => {
|
||||||
|
return '123';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 250,
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
const handleSearch = (e: any) => {
|
||||||
|
// console.log('handleSearch:', e);
|
||||||
|
params.value = e;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
*/
|
||||||
|
const handleAdd = () => {
|
||||||
|
router.push(`/media/device/Save`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActions = (
|
||||||
|
data: Partial<Record<string, any>>,
|
||||||
|
type: 'card' | 'table',
|
||||||
|
): ActionsType[] => {
|
||||||
|
if (!data) return [];
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
key: 'edit',
|
||||||
|
text: '编辑',
|
||||||
|
tooltip: {
|
||||||
|
title: '编辑',
|
||||||
|
},
|
||||||
|
icon: 'EditOutlined',
|
||||||
|
onClick: () => {
|
||||||
|
router.push(`/media/device/Save?id=${data.id}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'view',
|
||||||
|
text: '查看通道',
|
||||||
|
tooltip: {
|
||||||
|
title: '查看通道',
|
||||||
|
},
|
||||||
|
icon: 'PartitionOutlined',
|
||||||
|
onClick: () => {
|
||||||
|
router.push(
|
||||||
|
`/media/device/Channel?id=${data.id}&type=${data.provider}`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'debug',
|
||||||
|
text: '更新通道',
|
||||||
|
tooltip: {
|
||||||
|
title:
|
||||||
|
data.provider === 'fixed-media'
|
||||||
|
? '固定地址无法更新通道'
|
||||||
|
: data.state.value === 'offline'
|
||||||
|
? '设备已离线'
|
||||||
|
: data.state.value === 'notActive'
|
||||||
|
? '设备已禁用'
|
||||||
|
: '',
|
||||||
|
},
|
||||||
|
disabled:
|
||||||
|
data.state.value === 'offline' ||
|
||||||
|
data.state.value === 'notActive' ||
|
||||||
|
data.provider === 'fixed-media',
|
||||||
|
icon: 'SyncOutlined',
|
||||||
|
onClick: () => {
|
||||||
|
// updateChannel()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'delete',
|
||||||
|
text: '删除',
|
||||||
|
tooltip: {
|
||||||
|
title: '在线设备无法删除',
|
||||||
|
},
|
||||||
|
disabled: data.state.value === 'online',
|
||||||
|
popConfirm: {
|
||||||
|
title: '确认删除?',
|
||||||
|
onConfirm: async () => {
|
||||||
|
const resp = await DeviceApi.del(data.id);
|
||||||
|
if (resp.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
listRef.value?.reload();
|
||||||
|
} else {
|
||||||
|
message.error('操作失败!');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: 'DeleteOutlined',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return actions;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.page-container {
|
||||||
|
background: #f0f2f5;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,24 @@
|
||||||
|
type BaseItem = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
type State = {
|
||||||
|
value: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeviceItem = {
|
||||||
|
photoUrl?: string;
|
||||||
|
channelNumber: number;
|
||||||
|
createTime: number;
|
||||||
|
firmware: string;
|
||||||
|
gatewayId: string;
|
||||||
|
host: string;
|
||||||
|
manufacturer: string;
|
||||||
|
model: string;
|
||||||
|
port: number;
|
||||||
|
provider: string;
|
||||||
|
state: State;
|
||||||
|
streamMode: string;
|
||||||
|
transport: string;
|
||||||
|
} & BaseItem;
|
|
@ -36,10 +36,10 @@ onMounted(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const getData = () => {
|
const getData = () => {
|
||||||
homeApi.deviceCount().then((resp) => {
|
homeApi.deviceCount({}).then((resp) => {
|
||||||
deviceCount.value = resp.result;
|
deviceCount.value = resp.result;
|
||||||
});
|
});
|
||||||
homeApi.channelCount().then((resp) => {
|
homeApi.channelCount({}).then((resp) => {
|
||||||
channelCount.value = resp.result;
|
channelCount.value = resp.result;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
<template>
|
||||||
|
<div class="chart" ref="chart"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
const { proxy } = <any>getCurrentInstance();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// 图表数据
|
||||||
|
options:{
|
||||||
|
type:Object,
|
||||||
|
default:()=>{}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制图表
|
||||||
|
*/
|
||||||
|
const createChart = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
const myChart = echarts.init(proxy.$refs.chart);
|
||||||
|
myChart.setOption(props.options);
|
||||||
|
window.addEventListener('resize', function () {
|
||||||
|
myChart.resize();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.options,
|
||||||
|
() => createChart(),
|
||||||
|
{ immediate: true, deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,60 @@
|
||||||
|
<template>
|
||||||
|
<div class="home-title">
|
||||||
|
<div v-if="title">{{ title }}</div>
|
||||||
|
<div v-else class="title">
|
||||||
|
<slot name="title"></slot>
|
||||||
|
</div>
|
||||||
|
<div class="extra-text">
|
||||||
|
<slot name="extra"></slot>
|
||||||
|
</div>
|
||||||
|
<div class="home-title-english">{{ english }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="Guide">
|
||||||
|
interface guideProps {
|
||||||
|
title?: string;
|
||||||
|
english?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<guideProps>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.home-title {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-left: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 18px;
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background-color: @primary-color;
|
||||||
|
border: 1px solid #b4c0da;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
content: ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.title{
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-title-english {
|
||||||
|
position: absolute;
|
||||||
|
top: 30px;
|
||||||
|
color: rgba(0, 0, 0, 0.3);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,152 @@
|
||||||
|
<template>
|
||||||
|
<div class="new-alarm">
|
||||||
|
<div class="title">最新警告</div>
|
||||||
|
<div v-if="alarmList.length" class="new-alarm-items">
|
||||||
|
<ul>
|
||||||
|
<li v-for="item in alarmList.slice(0, 3)" :key="item">
|
||||||
|
<div class="new-alarm-item">
|
||||||
|
<div class="new-alarm-item-time">
|
||||||
|
<img
|
||||||
|
:src="getImage('/alarm/bashboard.png')"
|
||||||
|
alt=""
|
||||||
|
/>{{
|
||||||
|
moment(item.alarmTime).format(
|
||||||
|
'YYYY-MM-DD HH:mm:ss',
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div class="new-alarm-item-content">
|
||||||
|
<a-tooltip
|
||||||
|
:title="item.alarmName"
|
||||||
|
placement="topLeft"
|
||||||
|
>
|
||||||
|
<a>{{ item.alarmName }}</a>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="new-alarm-item-state">
|
||||||
|
<a-badge
|
||||||
|
:status="
|
||||||
|
item.state?.value === 'warning'
|
||||||
|
? 'error'
|
||||||
|
: 'default'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
</a-badge>
|
||||||
|
<span
|
||||||
|
:class="
|
||||||
|
item.state?.value === 'warning'
|
||||||
|
? 'error'
|
||||||
|
: 'default'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ item.state?.text }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'new-alarm-item-level',
|
||||||
|
`level-${item.level}`,
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ item.levelName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div v-else class="empty-body">
|
||||||
|
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE"></a-empty>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Empty } from 'ant-design-vue';
|
||||||
|
import { getImage } from '@/utils/comm';
|
||||||
|
import moment from 'moment';
|
||||||
|
const props = defineProps({
|
||||||
|
alarmList: {
|
||||||
|
type: Array,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.new-alarm {
|
||||||
|
background-color: white;
|
||||||
|
padding: 24px;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #e0e4e8;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.new-alarm-items {
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.new-alarm-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin: 18px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
.new-alarm-item-time {
|
||||||
|
width: 180px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
> img {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.new-alarm-item-content {
|
||||||
|
width: ~'calc(100% - 360px)';
|
||||||
|
}
|
||||||
|
.new-alarm-item-state {
|
||||||
|
width: 90px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
.error {
|
||||||
|
color: @error-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default {
|
||||||
|
color: @text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.new-alarm-item-level {
|
||||||
|
width: 52px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 2px;
|
||||||
|
|
||||||
|
&.level-1 {
|
||||||
|
background-color: #e50012;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.level-2 {
|
||||||
|
background-color: #ff9457;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.level-3 {
|
||||||
|
background-color: #fabd47;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.level-4 {
|
||||||
|
background-color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.level-5 {
|
||||||
|
background-color: #bbb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.empty-body {
|
||||||
|
height: 142px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,117 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-radio-group
|
||||||
|
v-if="quickBtn"
|
||||||
|
default-value="today"
|
||||||
|
button-style="solid"
|
||||||
|
v-model:value="radioValue"
|
||||||
|
@change="(e) => handleBtnChange(e.target.value)"
|
||||||
|
>
|
||||||
|
<a-radio-button
|
||||||
|
v-for="item in quickBtnList"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
<a-range-picker
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
valueFormat="YYYY-MM-DD HH:mm:ss"
|
||||||
|
style="margin-left: 12px"
|
||||||
|
@change="rangeChange"
|
||||||
|
v-model:value="rangeVal"
|
||||||
|
:allowClear="false"
|
||||||
|
>
|
||||||
|
</a-range-picker>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import moment from 'moment';
|
||||||
|
import { PropType } from 'vue';
|
||||||
|
|
||||||
|
interface BtnOptions {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EmitProps {
|
||||||
|
(e: 'change', data: Record<string, any>): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits<EmitProps>();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// 显示快捷按钮
|
||||||
|
quickBtn: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
// 快捷按钮列表
|
||||||
|
quickBtnList: {
|
||||||
|
type: Array as PropType<BtnOptions[]>,
|
||||||
|
default: [
|
||||||
|
{ label: '今日', value: 'today' },
|
||||||
|
{ label: '近一周', value: 'week' },
|
||||||
|
{ label: '近一月', value: 'month' },
|
||||||
|
{ label: '近一年', value: 'year' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'today',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const radioValue = ref(props.type || 'week' || undefined);
|
||||||
|
const rangeVal = ref<[string, string]>();
|
||||||
|
|
||||||
|
const rangeChange = (val: any) => {
|
||||||
|
radioValue.value = undefined;
|
||||||
|
emit('change', {
|
||||||
|
start: moment(val[0]).valueOf(),
|
||||||
|
end: moment(val[1]).valueOf(),
|
||||||
|
type: undefined,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTimeByType = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'hour':
|
||||||
|
return moment().subtract(1, 'hours').valueOf();
|
||||||
|
case 'week':
|
||||||
|
return moment().subtract(6, 'days').valueOf();
|
||||||
|
case 'month':
|
||||||
|
return moment().subtract(29, 'days').valueOf();
|
||||||
|
case 'year':
|
||||||
|
return moment().subtract(365, 'days').valueOf();
|
||||||
|
default:
|
||||||
|
return moment().startOf('day').valueOf();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBtnChange = (val: string) => {
|
||||||
|
radioValue.value = val;
|
||||||
|
let endTime = moment(new Date()).valueOf();
|
||||||
|
let startTime = getTimeByType(val);
|
||||||
|
if (val === 'yesterday') {
|
||||||
|
startTime = moment().subtract(1, 'days').startOf('day').valueOf();
|
||||||
|
endTime = moment().subtract(1, 'days').endOf('day').valueOf();
|
||||||
|
}
|
||||||
|
rangeVal.value = [
|
||||||
|
moment(startTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
moment(endTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
];
|
||||||
|
emit('change', {
|
||||||
|
start: startTime,
|
||||||
|
end: endTime,
|
||||||
|
type: val,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
handleBtnChange(radioValue.value);
|
||||||
|
watch(
|
||||||
|
() => radioValue.value,
|
||||||
|
{ deep: true, immediate: true },
|
||||||
|
);
|
||||||
|
</script>
|
|
@ -0,0 +1,106 @@
|
||||||
|
<template>
|
||||||
|
<div class="top-card">
|
||||||
|
<div class="top-card-content">
|
||||||
|
<div class="content-left">
|
||||||
|
<div class="content-left-title">
|
||||||
|
<span>{{ title }}</span>
|
||||||
|
<a-tooltip placement="top" v-if="tooltip">
|
||||||
|
<template #title>
|
||||||
|
<span>{{ tooltip }}</span>
|
||||||
|
</template>
|
||||||
|
<AIcon type="QuestionCircleOutlined" />
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="content-left-value">{{ value }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="content-right" v-if="img">
|
||||||
|
<img :src="img" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="content-right-echart" v-else>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="top-card-footer">
|
||||||
|
<template v-for="(item, index) in footer" :key="index">
|
||||||
|
<span v-if="!item.status">{{ item.title }}</span>
|
||||||
|
<a-badge v-else :text="item.title" :status="item.status" />
|
||||||
|
<div class="footer-item-value">{{ item.value }}</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { PropType } from 'vue';
|
||||||
|
import type { Footer } from '@/views/device/DashBoard/typings'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: { type: String, default: '' },
|
||||||
|
tooltip: { type: String, default: '' },
|
||||||
|
img: { type: String, default: '' },
|
||||||
|
footer: { type: Array as PropType<Footer[]>, default: '' },
|
||||||
|
value: { type: Number, default: 0 },
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.top-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
// height: 200px;
|
||||||
|
padding: 24px;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #e0e4e8;
|
||||||
|
border-radius: 2px;
|
||||||
|
.top-card-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: space-between;
|
||||||
|
.content-left {
|
||||||
|
height: 100%;
|
||||||
|
width: 50%;
|
||||||
|
&-title {
|
||||||
|
color: rgba(0, 0, 0, 0.64);
|
||||||
|
}
|
||||||
|
&-value {
|
||||||
|
padding: 12px 0;
|
||||||
|
color: #323130;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content-right {
|
||||||
|
width: 0;
|
||||||
|
height: 123px;
|
||||||
|
display: flex;
|
||||||
|
flex-grow: .7;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: flex-end;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content-right-echart{
|
||||||
|
height: 123px;
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.top-card-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
.footer-item-value {
|
||||||
|
color: #323130;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,547 @@
|
||||||
|
<template>
|
||||||
|
<page-container>
|
||||||
|
<div class="DashBoardBox">
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="6">
|
||||||
|
<TopCard
|
||||||
|
title="今日告警"
|
||||||
|
:value="state.today"
|
||||||
|
:footer="currentMonAlarm"
|
||||||
|
>
|
||||||
|
<Charts :options="state.fifteenOptions"></Charts>
|
||||||
|
</TopCard>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<TopCard
|
||||||
|
title="告警配置"
|
||||||
|
:value="state.config"
|
||||||
|
:footer="alarmState"
|
||||||
|
:img="getImage('/device/device-number.png')"
|
||||||
|
></TopCard>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<NewAlarm :alarm-list="state.alarmList"></NewAlarm>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="24">
|
||||||
|
<div class="alarm-card">
|
||||||
|
<Guide>
|
||||||
|
<template #title>
|
||||||
|
<span style="margin-right: 24px">告警统计</span>
|
||||||
|
<a-select
|
||||||
|
style="width: 40%"
|
||||||
|
v-model:value="queryCodition.targetType"
|
||||||
|
:options="
|
||||||
|
isNoCommunity ? selectOpt1 : selectOpt2
|
||||||
|
"
|
||||||
|
@change="selectChange"
|
||||||
|
></a-select>
|
||||||
|
</template>
|
||||||
|
<template #extra>
|
||||||
|
<TimeSelect
|
||||||
|
key="flow-static"
|
||||||
|
:type="'week'"
|
||||||
|
:quickBtnList="quickBtnList"
|
||||||
|
@change="initQueryTime"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Guide>
|
||||||
|
<div class="alarmBox">
|
||||||
|
<div class="alarmStatistics-chart">
|
||||||
|
<Charts
|
||||||
|
:options="alarmStatisticsOption"
|
||||||
|
></Charts>
|
||||||
|
</div>
|
||||||
|
<div class="alarmRank">
|
||||||
|
<h4>告警排名</h4>
|
||||||
|
<ul v-if="state.ranking.length" class="rankingList">
|
||||||
|
<li v-for="(item,i) in state.ranking" :key="item.targetId">
|
||||||
|
<img :src="getImage(`/rule-engine/dashboard/ranking/${i+1}.png`)" alt="">
|
||||||
|
<span class="rankingItemTitle" :title="item.targetName">{{item.targetName}}</span>
|
||||||
|
<span class="rankingItemValue">{{item.count}}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div v-else class="empty-body">
|
||||||
|
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE"></a-empty>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</page-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Empty } from 'ant-design-vue';
|
||||||
|
import { getImage } from '@/utils/comm';
|
||||||
|
import Charts from './components/Charts.vue';
|
||||||
|
import TopCard from './components/TopCard.vue';
|
||||||
|
import NewAlarm from './components/NewAlarm.vue';
|
||||||
|
import TimeSelect from './components/TimeSelect.vue';
|
||||||
|
import Guide from './components/Guide.vue';
|
||||||
|
import encodeQuery from '@/utils/encodeQuery';
|
||||||
|
import type { SelectTypes } from 'ant-design-vue/es/select';
|
||||||
|
import type { Footer } from '@/views/rule-engine/DashBoard/typings';
|
||||||
|
import { isNoCommunity } from '@/utils/utils';
|
||||||
|
import {
|
||||||
|
dashboard,
|
||||||
|
getAlarm,
|
||||||
|
getAlarmConfigCount,
|
||||||
|
getAlarmLevel,
|
||||||
|
} from '@/api/rule-engine/dashboard';
|
||||||
|
import moment from 'moment';
|
||||||
|
let currentMonAlarm = ref<Footer[]>([
|
||||||
|
{
|
||||||
|
title: '当月告警',
|
||||||
|
value: 0,
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
let alarmState = ref<Footer[]>([
|
||||||
|
{
|
||||||
|
title: '正常',
|
||||||
|
value: 0,
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '禁用',
|
||||||
|
value: 0,
|
||||||
|
status: 'error',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const selectOpt1 = ref<Object[]>([
|
||||||
|
{ label: '设备', value: 'device' },
|
||||||
|
{ label: '产品', value: 'product' },
|
||||||
|
{ label: '组织', value: 'org' },
|
||||||
|
{ label: '其它', value: 'other' },
|
||||||
|
]);
|
||||||
|
const selectOpt2 = ref<SelectTypes['options']>([
|
||||||
|
{ label: '设备', value: 'device' },
|
||||||
|
{ label: '产品', value: 'product' },
|
||||||
|
{ label: '其它', value: 'other' },
|
||||||
|
]);
|
||||||
|
let queryCodition = reactive({
|
||||||
|
startTime: 0,
|
||||||
|
endTime: 0,
|
||||||
|
targetType: 'device',
|
||||||
|
});
|
||||||
|
let alarmStatisticsOption = ref<any>({});
|
||||||
|
const quickBtnList = [
|
||||||
|
{ label: '昨日', value: 'yesterday' },
|
||||||
|
{ label: '近一周', value: 'week' },
|
||||||
|
{ label: '近一月', value: 'month' },
|
||||||
|
{ label: '近一年', value: 'year' },
|
||||||
|
];
|
||||||
|
type DashboardItem = {
|
||||||
|
group: string;
|
||||||
|
data: Record<string, any>;
|
||||||
|
};
|
||||||
|
let state = reactive<{
|
||||||
|
today: number;
|
||||||
|
thisMonth: number;
|
||||||
|
config: number;
|
||||||
|
enabledConfig: number;
|
||||||
|
disabledConfig: number;
|
||||||
|
alarmList: any[];
|
||||||
|
ranking: { targetId: string; targetName: string; count: number }[];
|
||||||
|
fifteenOptions: any;
|
||||||
|
}>({
|
||||||
|
today: 0,
|
||||||
|
thisMonth: 0,
|
||||||
|
config: 0,
|
||||||
|
enabledConfig: 0,
|
||||||
|
disabledConfig: 0,
|
||||||
|
alarmList: [],
|
||||||
|
ranking: [],
|
||||||
|
fifteenOptions: {},
|
||||||
|
});
|
||||||
|
// 今日告警
|
||||||
|
const today = {
|
||||||
|
dashboard: 'alarm',
|
||||||
|
object: 'record',
|
||||||
|
measurement: 'trend',
|
||||||
|
dimension: 'agg',
|
||||||
|
group: 'today',
|
||||||
|
params: {
|
||||||
|
time: '1d',
|
||||||
|
// targetType: 'device',
|
||||||
|
format: 'HH:mm:ss',
|
||||||
|
from: moment(new Date(new Date().setHours(0, 0, 0, 0))).format(
|
||||||
|
'YYYY-MM-DD HH:mm:ss',
|
||||||
|
),
|
||||||
|
to: 'now',
|
||||||
|
// limit: 24,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// 当月告警
|
||||||
|
const thisMonth = {
|
||||||
|
dashboard: 'alarm',
|
||||||
|
object: 'record',
|
||||||
|
measurement: 'trend',
|
||||||
|
dimension: 'agg',
|
||||||
|
group: 'thisMonth',
|
||||||
|
params: {
|
||||||
|
time: '1M',
|
||||||
|
// targetType: 'device',
|
||||||
|
format: 'yyyy-MM',
|
||||||
|
limit: 1,
|
||||||
|
from: 'now-1M',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const fifteen = {
|
||||||
|
dashboard: 'alarm',
|
||||||
|
object: 'record',
|
||||||
|
measurement: 'trend',
|
||||||
|
dimension: 'agg',
|
||||||
|
group: '15day',
|
||||||
|
params: {
|
||||||
|
time: '1d',
|
||||||
|
format: 'yyyy-MM-dd',
|
||||||
|
// targetType: 'product',
|
||||||
|
from: 'now-15d',
|
||||||
|
to: 'now',
|
||||||
|
limit: 15,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const getDashBoard = () => {
|
||||||
|
dashboard([today, thisMonth, fifteen]).then((res) => {
|
||||||
|
if (res.status == 200) {
|
||||||
|
const _data = res.result as DashboardItem[];
|
||||||
|
state.today = _data.find(
|
||||||
|
(item) => item.group === 'today',
|
||||||
|
)?.data.value;
|
||||||
|
state.thisMonth = _data.find(
|
||||||
|
(item) => item.group === 'thisMonth',
|
||||||
|
)?.data.value;
|
||||||
|
currentMonAlarm.value[0].value = state.thisMonth;
|
||||||
|
const fifteenData = _data
|
||||||
|
.filter((item) => item.group === '15day')
|
||||||
|
.map((item) => item.data)
|
||||||
|
.sort((a, b) => b.timestamp - a.timestamp);
|
||||||
|
state.fifteenOptions = {
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: fifteenData.map((item) => item.timeString),
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: '2%',
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '告警数',
|
||||||
|
data: fifteenData.map((item) => item.value),
|
||||||
|
type: 'bar',
|
||||||
|
itemStyle: {
|
||||||
|
color: '#2F54EB',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
getDashBoard();
|
||||||
|
const getAlarmConfig = async () => {
|
||||||
|
const countRes = await getAlarmConfigCount({});
|
||||||
|
const enabeldRes = await getAlarmConfigCount({
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
column: 'state',
|
||||||
|
value: 'enabled',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const disableRes = await getAlarmConfigCount({
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
column: 'state',
|
||||||
|
value: 'disabled',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (countRes.status == 200) {
|
||||||
|
state.config = countRes.result;
|
||||||
|
}
|
||||||
|
if (enabeldRes.status == 200) {
|
||||||
|
state.enabledConfig = enabeldRes.result;
|
||||||
|
alarmState.value[0].value = state.enabledConfig;
|
||||||
|
}
|
||||||
|
if (disableRes.status == 200) {
|
||||||
|
state.disabledConfig = disableRes.result;
|
||||||
|
alarmState.value[1].value = state.disabledConfig;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
getAlarmConfig();
|
||||||
|
const getCurrentAlarm = async () => {
|
||||||
|
const alarmLevel: any = await getAlarmLevel();
|
||||||
|
const sorts = { alarmTime: 'desc' };
|
||||||
|
const currentAlarm: any = await getAlarm(encodeQuery({ sorts }));
|
||||||
|
if (currentAlarm.status === 200) {
|
||||||
|
if (alarmLevel.status === 200) {
|
||||||
|
const levels = alarmLevel.result.levels;
|
||||||
|
state.alarmList = currentAlarm.result?.data
|
||||||
|
.filter((i: any) => i?.state?.value === 'warning')
|
||||||
|
.map((item: { level: any }) => ({
|
||||||
|
...item,
|
||||||
|
levelName: levels.find((l: any) => l.level === item.level)
|
||||||
|
?.title,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
state.alarmList = currentAlarm.result?.data.filter(
|
||||||
|
(item: any) => item?.state?.value === 'warning',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
getCurrentAlarm();
|
||||||
|
//初始化查询条件
|
||||||
|
const initQueryTime = (data: any) => {
|
||||||
|
queryCodition.startTime = data.start;
|
||||||
|
queryCodition.endTime = data.end;
|
||||||
|
console.log(queryCodition);
|
||||||
|
selectChange();
|
||||||
|
};
|
||||||
|
const selectChange = () => {
|
||||||
|
let time = '1h';
|
||||||
|
let format = 'HH';
|
||||||
|
let limit = 12;
|
||||||
|
const dt = queryCodition.endTime - queryCodition.startTime;
|
||||||
|
const hour = 60 * 60 * 1000;
|
||||||
|
const day = hour * 24;
|
||||||
|
const month = day * 30;
|
||||||
|
const year = 365 * day;
|
||||||
|
if (dt <= day) {
|
||||||
|
limit = Math.abs(Math.ceil(dt / hour));
|
||||||
|
} else if (dt > day && dt < year) {
|
||||||
|
limit = Math.abs(Math.ceil(dt / day)) + 1;
|
||||||
|
time = '1d';
|
||||||
|
format = 'M月dd日';
|
||||||
|
} else if (dt >= year) {
|
||||||
|
limit = Math.abs(Math.floor(dt / month));
|
||||||
|
time = '1M';
|
||||||
|
format = 'yyyy年-M月';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 告警趋势
|
||||||
|
const chartData = {
|
||||||
|
dashboard: 'alarm',
|
||||||
|
object: 'record',
|
||||||
|
measurement: 'trend',
|
||||||
|
dimension: 'agg',
|
||||||
|
group: 'alarmTrend',
|
||||||
|
params: {
|
||||||
|
targetType: queryCodition.targetType, // product、device、org、other
|
||||||
|
format: format,
|
||||||
|
time: time,
|
||||||
|
// from: 'now-1y', // now-1d、now-1w、now-1M、now-1y
|
||||||
|
// to: 'now',
|
||||||
|
limit: limit, // 12
|
||||||
|
// time: params.time.type === 'today' ? '1h' : '1d',
|
||||||
|
from: moment(queryCodition.startTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
to: moment(queryCodition.endTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
// limit: 30,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 告警排名
|
||||||
|
const order = {
|
||||||
|
dashboard: 'alarm',
|
||||||
|
object: 'record',
|
||||||
|
measurement: 'rank',
|
||||||
|
dimension: 'agg',
|
||||||
|
group: 'alarmRank',
|
||||||
|
params: {
|
||||||
|
// time: '1h',
|
||||||
|
time: time,
|
||||||
|
targetType: queryCodition.targetType,
|
||||||
|
from: moment(queryCodition.startTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
to: moment(queryCodition.endTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
limit: 9,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let tip = '其它';
|
||||||
|
if (queryCodition.targetType === 'device') {
|
||||||
|
tip = '设备';
|
||||||
|
} else if (queryCodition.targetType === 'product') {
|
||||||
|
tip = '产品';
|
||||||
|
} else if (queryCodition.targetType === 'org') {
|
||||||
|
tip = '组织';
|
||||||
|
}
|
||||||
|
// 网络请求
|
||||||
|
dashboard([chartData, order]).then((res) => {
|
||||||
|
if (res.status == 200) {
|
||||||
|
const xData: string[] = [];
|
||||||
|
const sData: number[] = [];
|
||||||
|
res.result
|
||||||
|
.filter((item: any) => item.group === 'alarmTrend')
|
||||||
|
.forEach((item: any) => {
|
||||||
|
xData.push(item.data.timeString);
|
||||||
|
sData.push(item.data.value);
|
||||||
|
});
|
||||||
|
alarmStatisticsOption.value = {
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: xData.reverse(),
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
// axisPointer: {
|
||||||
|
// type: 'shadow',
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: '2%',
|
||||||
|
bottom: '5%',
|
||||||
|
left: '24px',
|
||||||
|
right: '48px',
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: tip,
|
||||||
|
data: sData.reverse(),
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
color: '#685DEB',
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: '#685DEB', // 100% 处的颜色
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: '#FFFFFF', // 0% 处的颜色
|
||||||
|
},
|
||||||
|
],
|
||||||
|
global: false, // 缺省为 false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
state.ranking = res.result
|
||||||
|
?.filter((item: any) => item.group === 'alarmRank')
|
||||||
|
.map((d: { data: { value: any } }) => d.data?.value)
|
||||||
|
.sort(
|
||||||
|
(a: { count: number }, b: { count: number }) =>
|
||||||
|
b.count - a.count,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.alarm-card {
|
||||||
|
width: 100%;
|
||||||
|
background-color: white;
|
||||||
|
padding: 24px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
.alarmBox {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
.alarmStatistics-chart {
|
||||||
|
width: 70%;
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
.alarmRank {
|
||||||
|
position: relative;
|
||||||
|
width: 30%;
|
||||||
|
padding-left: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.rankingList {
|
||||||
|
margin: 25px 0 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 16px;
|
||||||
|
zoom: 1;
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
display: table;
|
||||||
|
content: ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
clear: both;
|
||||||
|
height: 0;
|
||||||
|
font-size: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
//color: red;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rankingItemNumber {
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-top: 1.5px;
|
||||||
|
margin-right: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #edf0f3;
|
||||||
|
border-radius: 20px;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #314659;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rankingItemTitle {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.empty-body {
|
||||||
|
height: 490px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
// height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,5 @@
|
||||||
|
export type Footer = {
|
||||||
|
title: string;
|
||||||
|
value: number | string;
|
||||||
|
status?: "default" | "error" | "success" | "warning" | "processing"
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<page-container>
|
<page-container>
|
||||||
<a-card>
|
<div>
|
||||||
<Search :columns="query.columns" target="device-instance" @search="handleSearch"></Search>
|
<Search :columns="query.columns" target="device-instance" @search="handleSearch"></Search>
|
||||||
<JTable
|
<JTable
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
@ -147,7 +147,7 @@
|
||||||
:title="title"
|
:title="title"
|
||||||
@success="refresh"
|
@success="refresh"
|
||||||
/>
|
/>
|
||||||
</a-card>
|
</div>
|
||||||
</page-container>
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -209,8 +209,8 @@ const columns = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '说明',
|
title: '说明',
|
||||||
dataIndex: 'describe',
|
dataIndex: 'description',
|
||||||
key: 'describe',
|
key: 'description',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<a-checkbox-group v-model:value="bulkList" :options="options" />
|
<a-checkbox-group v-model:value="bulkList" :options="options" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Search :columns="query.columns" @search="query.search" />
|
<Search :columns="props.queryColumns" @search="query.search" />
|
||||||
|
|
||||||
<JTable
|
<JTable
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
|
@ -118,6 +118,7 @@ import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
const emits = defineEmits(['confirm']);
|
const emits = defineEmits(['confirm']);
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
queryColumns: any[];
|
||||||
parentId: string;
|
parentId: string;
|
||||||
allPermission: dictType;
|
allPermission: dictType;
|
||||||
assetType: 'product' | 'device';
|
assetType: 'product' | 'device';
|
||||||
|
@ -139,7 +140,6 @@ const dialog = {
|
||||||
permission: item.selectPermissions,
|
permission: item.selectPermissions,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// console.log(params);
|
|
||||||
dialog.loading.value = true;
|
dialog.loading.value = true;
|
||||||
bindDeviceOrProductList_api(props.assetType, params)
|
bindDeviceOrProductList_api(props.assetType, params)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -334,6 +334,14 @@ const table: any = {
|
||||||
data.forEach((item) => {
|
data.forEach((item) => {
|
||||||
item.permissionList = permissionObj[item.id];
|
item.permissionList = permissionObj[item.id];
|
||||||
item.selectPermissions = ['read'];
|
item.selectPermissions = ['read'];
|
||||||
|
|
||||||
|
// 产品的状态进行转换处理
|
||||||
|
if(props.assetType === 'product') {
|
||||||
|
item.state = {
|
||||||
|
value: item.state === 1 ? 'online': item.state === 0 ? 'offline': '',
|
||||||
|
text: item.state === 1 ? '正常': item.state === 0 ? '禁用': ''
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
|
|
|
@ -117,8 +117,8 @@ const form = reactive({
|
||||||
form.loading = true;
|
form.loading = true;
|
||||||
const api = form.data.id ? updateDepartment_api : addDepartment_api;
|
const api = form.data.id ? updateDepartment_api : addDepartment_api;
|
||||||
api(form.data)
|
api(form.data)
|
||||||
.then(() => {
|
.then((resp:any) => {
|
||||||
emits('refresh');
|
emits('refresh',resp.result.id);
|
||||||
dialog.changeVisible(false);
|
dialog.changeVisible(false);
|
||||||
})
|
})
|
||||||
.finally(() => (form.loading = false));
|
.finally(() => (form.loading = false));
|
||||||
|
|
|
@ -46,7 +46,6 @@ const dialog = {
|
||||||
},
|
},
|
||||||
// 控制弹窗的打开与关闭
|
// 控制弹窗的打开与关闭
|
||||||
changeVisible: (ids: string[], permissionList: string[]) => {
|
changeVisible: (ids: string[], permissionList: string[]) => {
|
||||||
console.log(ids, permissionList);
|
|
||||||
form.permission = [...permissionList];
|
form.permission = [...permissionList];
|
||||||
form.assetIdList = ids;
|
form.assetIdList = ids;
|
||||||
options.value = setOptions(permissionList);
|
options.value = setOptions(permissionList);
|
||||||
|
|
|
@ -10,9 +10,17 @@
|
||||||
<search-outlined />
|
<search-outlined />
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-button type="primary" @click="openDialog" class="add-btn">
|
<div class="add-btn">
|
||||||
新增
|
<PermissionButton
|
||||||
</a-button>
|
type="primary"
|
||||||
|
class="add-btn"
|
||||||
|
:uhasPermission="`${permission}:add`"
|
||||||
|
@click="openDialog()"
|
||||||
|
>
|
||||||
|
新增
|
||||||
|
</PermissionButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a-tree
|
<a-tree
|
||||||
:tree-data="treeData"
|
:tree-data="treeData"
|
||||||
v-model:selected-keys="selectedKeys"
|
v-model:selected-keys="selectedKeys"
|
||||||
|
@ -21,14 +29,14 @@
|
||||||
>
|
>
|
||||||
<template #title="{ name, data }">
|
<template #title="{ name, data }">
|
||||||
<span>{{ name }}</span>
|
<span>{{ name }}</span>
|
||||||
<span class="func-btns">
|
<span class="func-btns" @click="(e) => e.stopPropagation()">
|
||||||
<a-tooltip>
|
<!-- <a-tooltip>
|
||||||
<template #title>编辑</template>
|
<template #title>编辑</template>
|
||||||
<a-button style="padding: 0" type="link">
|
<a-button style="padding: 0" type="link">
|
||||||
<edit-outlined @click="openDialog(data)" />
|
<edit-outlined @click="openDialog(data)" />
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip> -->
|
||||||
<a-tooltip>
|
<!-- <a-tooltip>
|
||||||
<template #title>新增子组织</template>
|
<template #title>新增子组织</template>
|
||||||
<a-button style="padding: 0" type="link">
|
<a-button style="padding: 0" type="link">
|
||||||
<plus-circle-outlined
|
<plus-circle-outlined
|
||||||
|
@ -42,9 +50,9 @@
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip> -->
|
||||||
|
|
||||||
<a-popconfirm
|
<!-- <a-popconfirm
|
||||||
title="确认删除"
|
title="确认删除"
|
||||||
ok-text="确定"
|
ok-text="确定"
|
||||||
cancel-text="取消"
|
cancel-text="取消"
|
||||||
|
@ -56,7 +64,45 @@
|
||||||
<delete-outlined />
|
<delete-outlined />
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-popconfirm>
|
</a-popconfirm> -->
|
||||||
|
|
||||||
|
<PermissionButton
|
||||||
|
:uhasPermission="`${permission}:update`"
|
||||||
|
type="link"
|
||||||
|
:tooltip="{
|
||||||
|
title: '新增子组织',
|
||||||
|
}"
|
||||||
|
@click="openDialog(data)"
|
||||||
|
>
|
||||||
|
<AIcon type="EditOutlined" />
|
||||||
|
</PermissionButton>
|
||||||
|
<PermissionButton
|
||||||
|
:uhasPermission="`${permission}:add`"
|
||||||
|
type="link"
|
||||||
|
:tooltip="{
|
||||||
|
title: '新增子组织',
|
||||||
|
}"
|
||||||
|
@click="
|
||||||
|
openDialog({
|
||||||
|
...data,
|
||||||
|
id: '',
|
||||||
|
parentId: data.id,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<AIcon type="PlusCircleOutlined" />
|
||||||
|
</PermissionButton>
|
||||||
|
<PermissionButton
|
||||||
|
type="link"
|
||||||
|
:uhasPermission="`${permission}:delete`"
|
||||||
|
:tooltip="{ title: '删除' }"
|
||||||
|
:popConfirm="{
|
||||||
|
title: `确定要删除吗`,
|
||||||
|
onConfirm: () => delDepartment(data.id),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<AIcon type="DeleteOutlined" />
|
||||||
|
</PermissionButton>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</a-tree>
|
</a-tree>
|
||||||
|
@ -65,38 +111,32 @@
|
||||||
<EditDepartmentDialog
|
<EditDepartmentDialog
|
||||||
:tree-data="sourceTree"
|
:tree-data="sourceTree"
|
||||||
ref="editDialogRef"
|
ref="editDialogRef"
|
||||||
@refresh="getTree"
|
@refresh="refresh"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||||
import { getTreeData_api, delDepartment_api } from '@/api/system/department';
|
import { getTreeData_api, delDepartment_api } from '@/api/system/department';
|
||||||
import { debounce, cloneDeep, omit } from 'lodash-es';
|
import { debounce, cloneDeep, omit } from 'lodash-es';
|
||||||
import { ArrayToTree } from '@/utils/utils';
|
import { ArrayToTree } from '@/utils/utils';
|
||||||
import EditDepartmentDialog from './EditDepartmentDialog.vue';
|
import EditDepartmentDialog from './EditDepartmentDialog.vue';
|
||||||
|
|
||||||
import {
|
import { SearchOutlined } from '@ant-design/icons-vue';
|
||||||
SearchOutlined,
|
|
||||||
EditOutlined,
|
|
||||||
PlusCircleOutlined,
|
|
||||||
DeleteOutlined,
|
|
||||||
} from '@ant-design/icons-vue';
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const permission = 'system/Department';
|
||||||
|
|
||||||
|
const save = useRoute().query.save;
|
||||||
const emits = defineEmits(['change']);
|
const emits = defineEmits(['change']);
|
||||||
const searchValue = ref('');// 搜索内容
|
const searchValue = ref(''); // 搜索内容
|
||||||
const loading = ref<boolean>(false); // 数据加载状态
|
const loading = ref<boolean>(false); // 数据加载状态
|
||||||
const sourceTree = ref<any[]>([]); // 源数据
|
const sourceTree = ref<any[]>([]); // 源数据
|
||||||
const treeMap = new Map(); // 数据的map版本
|
const treeMap = new Map(); // 数据的map版本
|
||||||
const treeData = ref<any[]>([]); // 展示的数据
|
const treeData = ref<any[]>([]); // 展示的数据
|
||||||
const selectedKeys = ref<string[]>([]); // 当前选中的项
|
const selectedKeys = ref<string[]>([]); // 当前选中的项
|
||||||
|
|
||||||
getTree();
|
|
||||||
watch(selectedKeys, (n) => {
|
|
||||||
emits('change', n[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
function getTree() {
|
function getTree() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const params = {
|
const params = {
|
||||||
|
@ -119,7 +159,7 @@ function getTree() {
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
const search = debounce(() => {
|
const search = debounce(() => {
|
||||||
const key = searchValue.value;
|
const key = searchValue.value;
|
||||||
const treeArray = new Map();
|
const treeArray = new Map();
|
||||||
|
@ -167,14 +207,30 @@ function delDepartment(id: string) {
|
||||||
getTree();
|
getTree();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function refresh(id: string) {
|
||||||
|
// @ts-ignore
|
||||||
|
window?.onSaveSuccess && window.onSaveSuccess(id);
|
||||||
|
window.close();
|
||||||
|
getTree();
|
||||||
|
}
|
||||||
|
|
||||||
// 弹窗
|
// 弹窗
|
||||||
const editDialogRef = ref(); // 新增弹窗实例
|
const editDialogRef = ref(); // 新增弹窗实例
|
||||||
const openDialog = (row: any = {}) => {
|
const openDialog = (row: any = {}) => {
|
||||||
editDialogRef.value.openDialog(true, row);
|
editDialogRef.value.openDialog(true, row);
|
||||||
};
|
};
|
||||||
|
init();
|
||||||
|
function init() {
|
||||||
|
getTree();
|
||||||
|
watch(selectedKeys, (n) => {
|
||||||
|
emits('change', n[0]);
|
||||||
|
});
|
||||||
|
if (save) {
|
||||||
|
nextTick(() => {
|
||||||
|
openDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@ -183,7 +239,10 @@ const openDialog = (row: any = {}) => {
|
||||||
|
|
||||||
.add-btn {
|
.add-btn {
|
||||||
margin: 24px 0;
|
margin: 24px 0;
|
||||||
width: 100%;
|
|
||||||
|
:deep(.ant-btn-primary) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.ant-tree-treenode) {
|
:deep(.ant-tree-treenode) {
|
||||||
|
@ -198,8 +257,9 @@ const openDialog = (row: any = {}) => {
|
||||||
.func-btns {
|
.func-btns {
|
||||||
display: none;
|
display: none;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
.ant-btn {
|
.ant-btn-link {
|
||||||
height: 22px;
|
padding: 0 4px;
|
||||||
|
height: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="visible"
|
||||||
|
title="绑定"
|
||||||
|
width="520px"
|
||||||
|
@ok="handleOk"
|
||||||
|
class="edit-dialog-container"
|
||||||
|
cancelText="取消"
|
||||||
|
okText="确定"
|
||||||
|
>
|
||||||
|
是否继续分配产品下的具体设备
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const emits = defineEmits(['confirm']);
|
||||||
|
|
||||||
|
const visible = ref<boolean>(false);
|
||||||
|
const handleOk = () => {
|
||||||
|
emits('confirm');
|
||||||
|
changeVisible();
|
||||||
|
};
|
||||||
|
// 控制弹窗的打开与关闭
|
||||||
|
const changeVisible = () => {
|
||||||
|
visible.value = !visible.value;
|
||||||
|
};
|
||||||
|
// 将打开弹窗的操作暴露给父组件
|
||||||
|
defineExpose({
|
||||||
|
openDialog: changeVisible,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
|
@ -14,29 +14,38 @@
|
||||||
>
|
>
|
||||||
<template #headerTitle>
|
<template #headerTitle>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button type="primary" @click="table.clickAdd">
|
<PermissionButton
|
||||||
<plus-outlined />资产分配
|
:uhasPermission="`${permission}:assert`"
|
||||||
</a-button>
|
type="primary"
|
||||||
|
@click="table.clickAdd"
|
||||||
|
>
|
||||||
|
<AIcon type="PlusOutlined" />资产分配
|
||||||
|
</PermissionButton>
|
||||||
<a-dropdown trigger="hover">
|
<a-dropdown trigger="hover">
|
||||||
<a-button>批量操作</a-button>
|
<a-button>批量操作</a-button>
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<a-menu>
|
<a-menu>
|
||||||
<a-menu-item>
|
<a-menu-item>
|
||||||
<a-popconfirm
|
<PermissionButton
|
||||||
title="是否批量解除绑定"
|
:uhasPermission="`${permission}:bind`"
|
||||||
ok-text="确定"
|
:popConfirm="{
|
||||||
cancel-text="取消"
|
title: `是否批量解除绑定`,
|
||||||
@confirm="table.clickUnBind()"
|
onConfirm: () =>
|
||||||
|
table.clickUnBind(),
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<a-button>
|
<AIcon
|
||||||
<DisconnectOutlined /> 批量解绑
|
type="DisconnectOutlined"
|
||||||
</a-button>
|
/>批量解绑
|
||||||
</a-popconfirm>
|
</PermissionButton>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item>
|
<a-menu-item>
|
||||||
<a-button @click="table.clickEdit()">
|
<PermissionButton
|
||||||
<EditOutlined /> 批量编辑
|
:uhasPermission="`${permission}:assert`"
|
||||||
</a-button>
|
@click="table.clickEdit()"
|
||||||
|
>
|
||||||
|
<AIcon type="EditOutlined" />批量编辑
|
||||||
|
</PermissionButton>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</template>
|
</template>
|
||||||
|
@ -102,21 +111,22 @@
|
||||||
</a-row>
|
</a-row>
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<a-button
|
<PermissionButton
|
||||||
|
:uhasPermission="`${permission}:assert`"
|
||||||
@click="table.clickEdit(slotProps)"
|
@click="table.clickEdit(slotProps)"
|
||||||
style="margin-right: 10px"
|
|
||||||
>
|
>
|
||||||
<AIcon type="EditOutlined" />
|
<AIcon type="EditOutlined" />
|
||||||
</a-button>
|
</PermissionButton>
|
||||||
<a-popconfirm
|
|
||||||
title="是否解除绑定"
|
<PermissionButton
|
||||||
ok-text="确定"
|
:uhasPermission="`${permission}:bind`"
|
||||||
cancel-text="取消"
|
:popConfirm="{
|
||||||
@confirm="table.clickUnBind(slotProps)"
|
title: `是否解除绑定`,
|
||||||
><a-button>
|
onConfirm: () => table.clickUnBind(slotProps),
|
||||||
<AIcon type="DisconnectOutlined" />
|
}"
|
||||||
</a-button>
|
>
|
||||||
</a-popconfirm>
|
<AIcon type="DisconnectOutlined" />
|
||||||
|
</PermissionButton>
|
||||||
</template>
|
</template>
|
||||||
</CardBox>
|
</CardBox>
|
||||||
</template>
|
</template>
|
||||||
|
@ -125,6 +135,7 @@
|
||||||
<div class="dialogs">
|
<div class="dialogs">
|
||||||
<AddDeviceOrProductDialog
|
<AddDeviceOrProductDialog
|
||||||
ref="addDialogRef"
|
ref="addDialogRef"
|
||||||
|
:query-columns="query.columns"
|
||||||
:parent-id="props.parentId"
|
:parent-id="props.parentId"
|
||||||
:all-permission="table.permissionList.value"
|
:all-permission="table.permissionList.value"
|
||||||
asset-type="device"
|
asset-type="device"
|
||||||
|
@ -142,11 +153,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" name="device">
|
<script setup lang="ts" name="device">
|
||||||
import {
|
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||||
PlusOutlined,
|
|
||||||
EditOutlined,
|
|
||||||
DisconnectOutlined,
|
|
||||||
} from '@ant-design/icons-vue';
|
|
||||||
import AddDeviceOrProductDialog from '../components/AddDeviceOrProductDialog.vue';
|
import AddDeviceOrProductDialog from '../components/AddDeviceOrProductDialog.vue';
|
||||||
import EditPermissionDialog from '../components/EditPermissionDialog.vue';
|
import EditPermissionDialog from '../components/EditPermissionDialog.vue';
|
||||||
import { getImage } from '@/utils/comm';
|
import { getImage } from '@/utils/comm';
|
||||||
|
@ -155,14 +163,19 @@ import {
|
||||||
getPermission_api,
|
getPermission_api,
|
||||||
getPermissionDict_api,
|
getPermissionDict_api,
|
||||||
unBindDeviceOrProduct_api,
|
unBindDeviceOrProduct_api,
|
||||||
|
getDeviceProduct_api,
|
||||||
} from '@/api/system/department';
|
} from '@/api/system/department';
|
||||||
import { intersection } from 'lodash-es';
|
import { intersection } from 'lodash-es';
|
||||||
|
|
||||||
import { dictType } from '../typing.d.ts';
|
import { dictType } from '../typing.d.ts';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const permission = 'system/Department';
|
||||||
|
|
||||||
|
const emits = defineEmits(['update:bindBool']);
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
parentId: string;
|
parentId: string;
|
||||||
|
bindBool: boolean;
|
||||||
}>();
|
}>();
|
||||||
const query = {
|
const query = {
|
||||||
columns: [
|
columns: [
|
||||||
|
@ -186,6 +199,41 @@ const query = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '所属产品',
|
||||||
|
dataIndex: 'productId$product-info',
|
||||||
|
key: 'productId$product-info',
|
||||||
|
ellipsis: true,
|
||||||
|
fixed: 'left',
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: () =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
const params = {
|
||||||
|
paging: false,
|
||||||
|
'sorts[0].name': 'createTime',
|
||||||
|
'sorts[0].order': 'desc',
|
||||||
|
};
|
||||||
|
getDeviceProduct_api(params).then((resp: any) => {
|
||||||
|
const result = resp.result.map((item: any) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '注册时间',
|
||||||
|
dataIndex: 'registryTime',
|
||||||
|
key: 'registryTime',
|
||||||
|
ellipsis: true,
|
||||||
|
fixed: 'left',
|
||||||
|
search: {
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
dataIndex: 'state',
|
dataIndex: 'state',
|
||||||
|
@ -279,26 +327,30 @@ const table = {
|
||||||
const { pageIndex, pageSize, total, data } =
|
const { pageIndex, pageSize, total, data } =
|
||||||
resp.result as resultType;
|
resp.result as resultType;
|
||||||
const ids = data.map((item) => item.id);
|
const ids = data.map((item) => item.id);
|
||||||
getPermission_api('device',ids, parentId).then((perResp: any) => {
|
getPermission_api('device', ids, parentId).then(
|
||||||
const permissionObj = {};
|
(perResp: any) => {
|
||||||
perResp.result.forEach((item: any) => {
|
const permissionObj = {};
|
||||||
permissionObj[item.assetId] = item.grantedPermissions;
|
perResp.result.forEach((item: any) => {
|
||||||
});
|
permissionObj[item.assetId] =
|
||||||
data.forEach(
|
item.grantedPermissions;
|
||||||
(item) => (item.permission = permissionObj[item.id]),
|
});
|
||||||
);
|
data.forEach(
|
||||||
|
(item) =>
|
||||||
|
(item.permission = permissionObj[item.id]),
|
||||||
|
);
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
code: 200,
|
code: 200,
|
||||||
result: {
|
result: {
|
||||||
data: data,
|
data: data,
|
||||||
pageIndex,
|
pageIndex,
|
||||||
pageSize,
|
pageSize,
|
||||||
total,
|
total,
|
||||||
},
|
},
|
||||||
status: 200,
|
status: 200,
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
// 整理参数并获取数据
|
// 整理参数并获取数据
|
||||||
|
@ -393,6 +445,10 @@ const addDialogRef = ref();
|
||||||
const editDialogRef = ref();
|
const editDialogRef = ref();
|
||||||
|
|
||||||
table.init();
|
table.init();
|
||||||
|
nextTick(() => {
|
||||||
|
props.bindBool && table.clickAdd();
|
||||||
|
emits('update:bindBool', false);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@ -406,7 +462,7 @@ table.init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.card-tools {
|
.card-tools {
|
||||||
.ant-btn {
|
span {
|
||||||
color: #252525;
|
color: #252525;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,19 @@
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<a-tabs v-model:activeKey="activeKey">
|
<a-tabs v-model:activeKey="activeKey">
|
||||||
<a-tab-pane key="product" tab="产品">
|
<a-tab-pane key="product" tab="产品">
|
||||||
<Product :parentId="departmentId" />
|
<Product
|
||||||
|
:parentId="departmentId"
|
||||||
|
@open-device-bind="openDeviceBind"
|
||||||
|
/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="device" tab="设备">
|
<a-tab-pane key="device" tab="设备">
|
||||||
<Device :parentId="departmentId" />
|
<Device
|
||||||
|
:parentId="departmentId"
|
||||||
|
v-model:bindBool="bindBool"
|
||||||
|
/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="user" tab="用户">
|
<a-tab-pane key="user" tab="用户">
|
||||||
<User />
|
<User :parentId="departmentId" />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,6 +36,12 @@ import User from './user/index.vue';
|
||||||
const activeKey = ref<'product' | 'device' | 'user'>('product');
|
const activeKey = ref<'product' | 'device' | 'user'>('product');
|
||||||
|
|
||||||
const departmentId = ref<string>('');
|
const departmentId = ref<string>('');
|
||||||
|
|
||||||
|
const bindBool = ref<boolean>(false);
|
||||||
|
const openDeviceBind = () => {
|
||||||
|
bindBool.value = true;
|
||||||
|
activeKey.value = 'device';
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@ -43,8 +55,9 @@ const departmentId = ref<string>('');
|
||||||
flex-basis: 300px;
|
flex-basis: 300px;
|
||||||
}
|
}
|
||||||
.right {
|
.right {
|
||||||
flex: 1 1 auto;
|
width: calc(100% - 300px);
|
||||||
.ant-tabs-nav {
|
|
||||||
|
.ant-tabs-nav-wrap {
|
||||||
padding-left: 24px;
|
padding-left: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,29 +14,38 @@
|
||||||
>
|
>
|
||||||
<template #headerTitle>
|
<template #headerTitle>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button type="primary" @click="table.clickAdd">
|
<PermissionButton
|
||||||
<plus-outlined />资产分配
|
:uhasPermission="`${permission}:assert`"
|
||||||
</a-button>
|
type="primary"
|
||||||
|
@click="table.clickAdd"
|
||||||
|
>
|
||||||
|
<AIcon type="PlusOutlined" />资产分配
|
||||||
|
</PermissionButton>
|
||||||
<a-dropdown trigger="hover">
|
<a-dropdown trigger="hover">
|
||||||
<a-button>批量操作</a-button>
|
<a-button>批量操作</a-button>
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<a-menu>
|
<a-menu>
|
||||||
<a-menu-item>
|
<a-menu-item>
|
||||||
<a-popconfirm
|
<PermissionButton
|
||||||
title="是否批量解除绑定"
|
:uhasPermission="`${permission}:bind`"
|
||||||
ok-text="确定"
|
:popConfirm="{
|
||||||
cancel-text="取消"
|
title: `是否批量解除绑定`,
|
||||||
@confirm="table.clickUnBind()"
|
onConfirm: () =>
|
||||||
|
table.clickUnBind(),
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<a-button>
|
<AIcon
|
||||||
<DisconnectOutlined /> 批量解绑
|
type="DisconnectOutlined"
|
||||||
</a-button>
|
/>批量解绑
|
||||||
</a-popconfirm>
|
</PermissionButton>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item>
|
<a-menu-item>
|
||||||
<a-button @click="table.clickEdit()">
|
<PermissionButton
|
||||||
<EditOutlined /> 批量编辑
|
:uhasPermission="`${permission}:assert`"
|
||||||
</a-button>
|
@click="()=>table.clickEdit()"
|
||||||
|
>
|
||||||
|
<AIcon type="EditOutlined" />批量编辑
|
||||||
|
</PermissionButton>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</template>
|
</template>
|
||||||
|
@ -102,21 +111,22 @@
|
||||||
</a-row>
|
</a-row>
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<a-button
|
<PermissionButton
|
||||||
@click="table.clickEdit(slotProps)"
|
:uhasPermission="`${permission}:assert`"
|
||||||
style="margin-right: 10px"
|
@click="() => table.clickEdit(slotProps)"
|
||||||
>
|
>
|
||||||
<AIcon type="EditOutlined" />
|
<AIcon type="EditOutlined" />
|
||||||
</a-button>
|
</PermissionButton>
|
||||||
<a-popconfirm
|
|
||||||
title="是否解除绑定"
|
<PermissionButton
|
||||||
ok-text="确定"
|
:uhasPermission="`${permission}:bind`"
|
||||||
cancel-text="取消"
|
:popConfirm="{
|
||||||
@confirm="table.clickUnBind(slotProps)"
|
title: `是否解除绑定`,
|
||||||
><a-button>
|
onConfirm: () => table.clickUnBind(slotProps),
|
||||||
<AIcon type="DisconnectOutlined" />
|
}"
|
||||||
</a-button>
|
>
|
||||||
</a-popconfirm>
|
<AIcon type="DisconnectOutlined" />
|
||||||
|
</PermissionButton>
|
||||||
</template>
|
</template>
|
||||||
</CardBox>
|
</CardBox>
|
||||||
</template>
|
</template>
|
||||||
|
@ -125,10 +135,11 @@
|
||||||
<div class="dialogs">
|
<div class="dialogs">
|
||||||
<AddDeviceOrProductDialog
|
<AddDeviceOrProductDialog
|
||||||
ref="addDialogRef"
|
ref="addDialogRef"
|
||||||
|
:query-columns="query.columns"
|
||||||
:parent-id="props.parentId"
|
:parent-id="props.parentId"
|
||||||
:all-permission="table.permissionList.value"
|
:all-permission="table.permissionList.value"
|
||||||
asset-type="product"
|
asset-type="product"
|
||||||
@confirm="table.refresh"
|
@confirm="table.addConfirm"
|
||||||
/>
|
/>
|
||||||
<EditPermissionDialog
|
<EditPermissionDialog
|
||||||
ref="editDialogRef"
|
ref="editDialogRef"
|
||||||
|
@ -137,18 +148,20 @@
|
||||||
asset-type="product"
|
asset-type="product"
|
||||||
@confirm="table.refresh"
|
@confirm="table.refresh"
|
||||||
/>
|
/>
|
||||||
|
<NextDialog
|
||||||
|
ref="nextDialogRef"
|
||||||
|
@confirm="emits('openDeviceBind')"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" name="product">
|
<script setup lang="ts" name="product">
|
||||||
import {
|
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||||
PlusOutlined,
|
|
||||||
EditOutlined,
|
|
||||||
DisconnectOutlined,
|
|
||||||
} from '@ant-design/icons-vue';
|
|
||||||
import AddDeviceOrProductDialog from '../components/AddDeviceOrProductDialog.vue';
|
import AddDeviceOrProductDialog from '../components/AddDeviceOrProductDialog.vue';
|
||||||
import EditPermissionDialog from '../components/EditPermissionDialog.vue';
|
import EditPermissionDialog from '../components/EditPermissionDialog.vue';
|
||||||
|
import NextDialog from '../components/NextDialog.vue';
|
||||||
import { getImage } from '@/utils/comm';
|
import { getImage } from '@/utils/comm';
|
||||||
import {
|
import {
|
||||||
getDeviceOrProductList_api,
|
getDeviceOrProductList_api,
|
||||||
|
@ -161,6 +174,9 @@ import { intersection } from 'lodash-es';
|
||||||
import { dictType } from '../typing.d.ts';
|
import { dictType } from '../typing.d.ts';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const permission = 'system/Department';
|
||||||
|
|
||||||
|
const emits = defineEmits(['openDeviceBind']);
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
parentId: string;
|
parentId: string;
|
||||||
}>();
|
}>();
|
||||||
|
@ -196,16 +212,12 @@ const query = {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: '在线',
|
label: '正常',
|
||||||
value: 'online',
|
value: 1,
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '离线',
|
|
||||||
value: 'offline',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '禁用',
|
label: '禁用',
|
||||||
value: 'notActive',
|
value: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -279,26 +291,43 @@ const table = {
|
||||||
const { pageIndex, pageSize, total, data } =
|
const { pageIndex, pageSize, total, data } =
|
||||||
resp.result as resultType;
|
resp.result as resultType;
|
||||||
const ids = data.map((item) => item.id);
|
const ids = data.map((item) => item.id);
|
||||||
getPermission_api('product', ids, parentId).then((perResp: any) => {
|
getPermission_api('product', ids, parentId).then(
|
||||||
const permissionObj = {};
|
(perResp: any) => {
|
||||||
perResp.result.forEach((item: any) => {
|
const permissionObj = {};
|
||||||
permissionObj[item.assetId] = item.grantedPermissions;
|
perResp.result.forEach((item: any) => {
|
||||||
});
|
permissionObj[item.assetId] =
|
||||||
data.forEach(
|
item.grantedPermissions;
|
||||||
(item) => (item.permission = permissionObj[item.id]),
|
});
|
||||||
);
|
data.forEach((item) => {
|
||||||
|
item.permission = permissionObj[item.id];
|
||||||
|
item.state = {
|
||||||
|
value:
|
||||||
|
item.state === 1
|
||||||
|
? 'online'
|
||||||
|
: item.state === 0
|
||||||
|
? 'offline'
|
||||||
|
: '',
|
||||||
|
text:
|
||||||
|
item.state === 1
|
||||||
|
? '正常'
|
||||||
|
: item.state === 0
|
||||||
|
? '禁用'
|
||||||
|
: '',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
code: 200,
|
code: 200,
|
||||||
result: {
|
result: {
|
||||||
data: data,
|
data: data,
|
||||||
pageIndex,
|
pageIndex,
|
||||||
pageSize,
|
pageSize,
|
||||||
total,
|
total,
|
||||||
},
|
},
|
||||||
status: 200,
|
status: 200,
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
// 整理参数并获取数据
|
// 整理参数并获取数据
|
||||||
|
@ -346,10 +375,13 @@ const table = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clickAdd: () => {
|
clickAdd: () => {
|
||||||
|
console.log(222)
|
||||||
|
console.log(addDialogRef.value)
|
||||||
addDialogRef.value && addDialogRef.value.openDialog();
|
addDialogRef.value && addDialogRef.value.openDialog();
|
||||||
},
|
},
|
||||||
clickEdit: (row?: any) => {
|
clickEdit: (row?: any) => {
|
||||||
const ids = row ? [row.id] : [...table._selectedRowKeys.value];
|
const ids = row ? [row.id] : [...table._selectedRowKeys.value];
|
||||||
|
|
||||||
if (row || table.selectedRows.length === 1) {
|
if (row || table.selectedRows.length === 1) {
|
||||||
const permissionList =
|
const permissionList =
|
||||||
row?.permission || table.selectedRows[0].permission;
|
row?.permission || table.selectedRows[0].permission;
|
||||||
|
@ -387,11 +419,15 @@ const table = {
|
||||||
tableRef.value.reload();
|
tableRef.value.reload();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
addConfirm: () => {
|
||||||
|
table.refresh();
|
||||||
|
nextDialogRef.value && nextDialogRef.value.openDialog();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const addDialogRef = ref();
|
const addDialogRef = ref();
|
||||||
const editDialogRef = ref();
|
const editDialogRef = ref();
|
||||||
|
const nextDialogRef = ref();
|
||||||
table.init();
|
table.init();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -406,7 +442,7 @@ table.init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.card-tools {
|
.card-tools {
|
||||||
.ant-btn {
|
span {
|
||||||
color: #252525;
|
color: #252525;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
class="add-bind-user-dialog-container"
|
||||||
|
title="绑定"
|
||||||
|
width="1440px"
|
||||||
|
@ok="dialog.handleOk"
|
||||||
|
centered
|
||||||
|
:confirmLoading="dialog.loading.value"
|
||||||
|
cancelText="取消"
|
||||||
|
okText="确定"
|
||||||
|
v-model:visible="dialog.visible.value"
|
||||||
|
>
|
||||||
|
<Search :columns="query.columns" @search="query.search" />
|
||||||
|
<div class="table">
|
||||||
|
<JTable
|
||||||
|
ref="tableRef"
|
||||||
|
:columns="table.columns"
|
||||||
|
:request="table.requestFun"
|
||||||
|
:params="query.params"
|
||||||
|
:rowSelection="{
|
||||||
|
selectedRowKeys: table._selectedRowKeys,
|
||||||
|
onChange: table.onSelectChange,
|
||||||
|
}"
|
||||||
|
@cancelSelect="table.cancelSelect"
|
||||||
|
model="TABLE"
|
||||||
|
:defaultParams="{
|
||||||
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { bindUser_api, getBindUserList_api } from '@/api/system/department';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const emits = defineEmits(['confirm']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
parentId: String,
|
||||||
|
});
|
||||||
|
// 弹窗相关
|
||||||
|
const dialog = {
|
||||||
|
loading: ref<boolean>(false),
|
||||||
|
visible: ref<boolean>(false),
|
||||||
|
handleOk: () => {
|
||||||
|
if (table._selectedRowKeys.length && props.parentId) {
|
||||||
|
bindUser_api(props.parentId, table._selectedRowKeys).then(() => {
|
||||||
|
emits('confirm');
|
||||||
|
message.success('操作成功');
|
||||||
|
dialog.changeVisible();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dialog.changeVisible();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 控制弹窗的打开与关闭
|
||||||
|
changeVisible: () => {
|
||||||
|
if (!dialog.visible.value) query.search({});
|
||||||
|
dialog.visible.value = !dialog.visible.value;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// 将打开弹窗的操作暴露给父组件
|
||||||
|
defineExpose({
|
||||||
|
openDialog: dialog.changeVisible,
|
||||||
|
});
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: '姓名',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
ellipsis: true,
|
||||||
|
fixed: 'left',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户名',
|
||||||
|
dataIndex: 'username',
|
||||||
|
key: 'username',
|
||||||
|
ellipsis: true,
|
||||||
|
fixed: 'left',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
params: ref({}),
|
||||||
|
search: (params: any) => {
|
||||||
|
query.params.value = params;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const table = reactive({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: '姓名',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户名',
|
||||||
|
dataIndex: 'username',
|
||||||
|
key: 'username',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
_selectedRowKeys: [] as string[],
|
||||||
|
|
||||||
|
requestFun: async (oParams: any) => {
|
||||||
|
table.cancelSelect();
|
||||||
|
if (props.parentId) {
|
||||||
|
const params = {
|
||||||
|
...oParams,
|
||||||
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
|
terms: [
|
||||||
|
...oParams.terms,
|
||||||
|
{
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
column: 'id$in-dimension$org$not',
|
||||||
|
value: props.parentId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const resp: any = await getBindUserList_api(params);
|
||||||
|
return {
|
||||||
|
code: resp.status,
|
||||||
|
result: resp.result,
|
||||||
|
status: resp.status,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
result: {
|
||||||
|
data: [],
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 0,
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
|
status: 200,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSelectChange: (keys: string[]) => {
|
||||||
|
table._selectedRowKeys = keys;
|
||||||
|
},
|
||||||
|
cancelSelect: () => {
|
||||||
|
table._selectedRowKeys = [];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(.add-bind-user-dialog-container) {
|
||||||
|
.table {
|
||||||
|
height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,13 +1,230 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
用户
|
<Search :columns="query.columns" @search="query.search" />
|
||||||
|
|
||||||
|
<JTable
|
||||||
|
ref="tableRef"
|
||||||
|
:columns="table.columns"
|
||||||
|
:request="table.requestFun"
|
||||||
|
:params="query.params"
|
||||||
|
:rowSelection="{
|
||||||
|
selectedRowKeys: table._selectedRowKeys,
|
||||||
|
onChange: table.onSelectChange,
|
||||||
|
}"
|
||||||
|
@cancelSelect="table.cancelSelect"
|
||||||
|
model="TABLE"
|
||||||
|
>
|
||||||
|
<template #headerTitle>
|
||||||
|
<PermissionButton
|
||||||
|
type="primary"
|
||||||
|
:uhasPermission="`${permission}:bind-user`"
|
||||||
|
@click="table.openDialog"
|
||||||
|
style="margin-right: 15px;"
|
||||||
|
>
|
||||||
|
<AIcon type="PlusOutlined" />绑定用户
|
||||||
|
</PermissionButton>
|
||||||
|
<div style="display: inline-block;width: 12px;height: 1px;"></div>
|
||||||
|
<PermissionButton
|
||||||
|
:uhasPermission="`${permission}:bind`"
|
||||||
|
:popConfirm="{
|
||||||
|
title: `是否解除绑定`,
|
||||||
|
onConfirm: () => table.unBind(),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<AIcon type="DisconnectOutlined" />批量解绑
|
||||||
|
</PermissionButton>
|
||||||
|
</template>
|
||||||
|
<template #status="slotProps">
|
||||||
|
<BadgeStatus
|
||||||
|
:status="slotProps.status"
|
||||||
|
:text="slotProps.status ? '正常' : '禁用'"
|
||||||
|
:statusNames="{
|
||||||
|
1: 'success',
|
||||||
|
0: 'error',
|
||||||
|
}"
|
||||||
|
></BadgeStatus>
|
||||||
|
</template>
|
||||||
|
<template #action="slotProps">
|
||||||
|
<a-space :size="16">
|
||||||
|
<PermissionButton
|
||||||
|
type="link"
|
||||||
|
:uhasPermission="`${permission}:bind`"
|
||||||
|
:popConfirm="{
|
||||||
|
title: `是否解除绑定`,
|
||||||
|
onConfirm: () => table.unBind(slotProps),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<AIcon type="DisconnectOutlined" />
|
||||||
|
</PermissionButton>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</JTable>
|
||||||
|
|
||||||
|
<div class="dialogs">
|
||||||
|
<AddBindUserDialog
|
||||||
|
ref="addDialogRef"
|
||||||
|
:parent-id="props.parentId"
|
||||||
|
@confirm="table.refresh"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts" name="user">
|
||||||
|
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||||
|
import AddBindUserDialog from './components/addBindUserDialog.vue';
|
||||||
|
import { getBindUserList_api, unBindUser_api } from '@/api/system/department';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const permission = 'system/Department';
|
||||||
|
|
||||||
|
const addDialogRef = ref();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
parentId: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: '姓名',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
ellipsis: true,
|
||||||
|
fixed: 'left',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户名',
|
||||||
|
dataIndex: 'username',
|
||||||
|
key: 'username',
|
||||||
|
ellipsis: true,
|
||||||
|
fixed: 'left',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'state',
|
||||||
|
key: 'state',
|
||||||
|
ellipsis: true,
|
||||||
|
fixed: 'left',
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: '正常',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '禁用',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
params: ref({}),
|
||||||
|
search: (params: any) => {
|
||||||
|
query.params.value = params;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表格
|
||||||
|
const tableRef = ref<Record<string, any>>({}); // 表格实例
|
||||||
|
const table = reactive({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: '姓名',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户名',
|
||||||
|
dataIndex: 'username',
|
||||||
|
key: 'username',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'action',
|
||||||
|
key: 'action',
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
_selectedRowKeys: [] as string[],
|
||||||
|
|
||||||
|
requestFun: async (oParams: any) => {
|
||||||
|
table.cancelSelect();
|
||||||
|
if (props.parentId) {
|
||||||
|
const params = {
|
||||||
|
...oParams,
|
||||||
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
|
terms: [
|
||||||
|
...oParams.terms,
|
||||||
|
{
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
column: 'id$in-dimension$org',
|
||||||
|
value: props.parentId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const resp: any = await getBindUserList_api(params);
|
||||||
|
return {
|
||||||
|
code: resp.status,
|
||||||
|
result: resp.result,
|
||||||
|
status: resp.status,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
result: {
|
||||||
|
data: [],
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 0,
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
|
status: 200,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unBind: (row?: any) => {
|
||||||
|
const ids = row ? [row.id] : table._selectedRowKeys;
|
||||||
|
if (ids.length < 1) return message.warning('请勾选需要解绑的数据');
|
||||||
|
|
||||||
|
unBindUser_api(props.parentId, ids).then(() => {
|
||||||
|
message.success('操作成功');
|
||||||
|
table.refresh();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 打开编辑弹窗
|
||||||
|
openDialog: () => {
|
||||||
|
addDialogRef.value && addDialogRef.value.openDialog();
|
||||||
|
},
|
||||||
|
onSelectChange: (keys: string[]) => {
|
||||||
|
table._selectedRowKeys = keys;
|
||||||
|
},
|
||||||
|
cancelSelect: () => {
|
||||||
|
table._selectedRowKeys = [];
|
||||||
|
},
|
||||||
|
// 刷新列表
|
||||||
|
refresh: () => {
|
||||||
|
tableRef.value.reload();
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
import { FormInstance, message } from 'ant-design-vue';
|
import { FormInstance, message } from 'ant-design-vue';
|
||||||
import { saveRole_api } from '@/api/system/role';
|
import { saveRole_api } from '@/api/system/role';
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
// 弹窗相关
|
// 弹窗相关
|
||||||
const dialog = reactive({
|
const dialog = reactive({
|
||||||
visible: false,
|
visible: false,
|
||||||
|
@ -56,12 +57,17 @@ const dialog = reactive({
|
||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
message.success('操作成功');
|
message.success('操作成功');
|
||||||
dialog.visible = false;
|
dialog.visible = false;
|
||||||
router.push(`/system/Role/detail/${resp.result.id}`);
|
|
||||||
|
if (route.query.save) {
|
||||||
|
// @ts-ignore
|
||||||
|
window?.onSaveSuccess && window.onSaveSuccess(resp.result.id);
|
||||||
|
window.close();
|
||||||
|
} else router.push(`/system/Role/detail/${resp.result.id}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 控制弹窗的打开与关闭
|
// 控制弹窗的打开与关闭
|
||||||
changeVisible: (status: boolean, defaultForm: object={}) => {
|
changeVisible: (status: boolean, defaultForm: object = {}) => {
|
||||||
dialog.visible = status;
|
dialog.visible = status;
|
||||||
form.data = { name: '', description: '', ...defaultForm };
|
form.data = { name: '', description: '', ...defaultForm };
|
||||||
},
|
},
|
||||||
|
@ -76,12 +82,10 @@ const form = reactive({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 将打开弹窗的操作暴露给父组件
|
// 将打开弹窗的操作暴露给父组件
|
||||||
defineExpose({
|
defineExpose({
|
||||||
openDialog: dialog.changeVisible
|
openDialog: dialog.changeVisible,
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
@ -10,36 +10,38 @@
|
||||||
:params="query.params"
|
:params="query.params"
|
||||||
>
|
>
|
||||||
<template #headerTitle>
|
<template #headerTitle>
|
||||||
<a-button type="primary" @click="table.clickAdd"
|
<PermissionButton
|
||||||
><plus-outlined />新增</a-button
|
type="primary"
|
||||||
|
:uhasPermission="`${permission}:add`"
|
||||||
|
@click="table.clickAdd"
|
||||||
>
|
>
|
||||||
|
<AIcon type="PlusOutlined" />新增
|
||||||
|
</PermissionButton>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #action="slotProps">
|
<template #action="slotProps">
|
||||||
<a-space :size="16">
|
<a-space :size="16">
|
||||||
<a-tooltip>
|
<PermissionButton
|
||||||
<template #title>编辑</template>
|
:uhasPermission="`${permission}:update`"
|
||||||
<a-button
|
type="link"
|
||||||
style="padding: 0"
|
:tooltip="{
|
||||||
type="link"
|
title: '编辑',
|
||||||
@click="table.clickEdit(slotProps)"
|
}"
|
||||||
>
|
@click="table.clickEdit(slotProps)"
|
||||||
<edit-outlined />
|
|
||||||
</a-button>
|
|
||||||
</a-tooltip>
|
|
||||||
<a-popconfirm
|
|
||||||
title="确定要删除吗?"
|
|
||||||
ok-text="确定"
|
|
||||||
cancel-text="取消"
|
|
||||||
@confirm="table.clickDel(slotProps)"
|
|
||||||
>
|
>
|
||||||
<a-tooltip>
|
<AIcon type="EditOutlined" />
|
||||||
<template #title>删除</template>
|
</PermissionButton>
|
||||||
<a-button style="padding: 0" type="link">
|
<PermissionButton
|
||||||
<delete-outlined />
|
type="link"
|
||||||
</a-button>
|
:uhasPermission="`${permission}:delete`"
|
||||||
</a-tooltip>
|
:tooltip="{ title: '删除' }"
|
||||||
</a-popconfirm>
|
:popConfirm="{
|
||||||
|
title: `确定要删除吗`,
|
||||||
|
onConfirm: () => table.clickDel(slotProps),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<AIcon type="DeleteOutlined" />
|
||||||
|
</PermissionButton>
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
</JTable>
|
</JTable>
|
||||||
|
@ -51,14 +53,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" name="Role">
|
<script setup lang="ts" name="Role">
|
||||||
import {
|
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||||
EditOutlined,
|
|
||||||
DeleteOutlined,
|
|
||||||
PlusOutlined,
|
|
||||||
} from '@ant-design/icons-vue';
|
|
||||||
import AddDialog from './components/AddDialog.vue';
|
import AddDialog from './components/AddDialog.vue';
|
||||||
import { getRoleList_api, delRole_api } from '@/api/system/role';
|
import { getRoleList_api, delRole_api } from '@/api/system/role';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const permission = 'system/Role';
|
||||||
|
|
||||||
const addDialogRef = ref(); // 新增弹窗实例
|
const addDialogRef = ref(); // 新增弹窗实例
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
@ -143,4 +144,14 @@ nextTick(() => {
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped>
|
||||||
|
.role-container {
|
||||||
|
|
||||||
|
:deep(.ant-table-cell) {
|
||||||
|
.ant-btn-link {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,433 @@
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="dialog.visible"
|
||||||
|
:title="dialog.title"
|
||||||
|
width="675px"
|
||||||
|
@ok="dialog.handleOk"
|
||||||
|
class="edit-dialog-container"
|
||||||
|
:confirmLoading="dialog.loading"
|
||||||
|
cancelText="取消"
|
||||||
|
okText="确定"
|
||||||
|
>
|
||||||
|
<a-form ref="formRef" :model="form.data" layout="vertical">
|
||||||
|
<a-row :gutter="24" v-if="form.IsShow('add', 'edit')">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item
|
||||||
|
name="name"
|
||||||
|
label="姓名"
|
||||||
|
:rules="[
|
||||||
|
{ required: true, message: '请输入姓名' },
|
||||||
|
{
|
||||||
|
max: 64,
|
||||||
|
message: '最多可输入64个字符',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="form.data.name"
|
||||||
|
placeholder="请输入姓名"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item
|
||||||
|
name="username"
|
||||||
|
label="用户名"
|
||||||
|
:rules="[
|
||||||
|
{ required: true },
|
||||||
|
{
|
||||||
|
validator: form.rules.checkUserName,
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="form.data.username"
|
||||||
|
placeholder="请输入用户名"
|
||||||
|
:disabled="dialog.type === 'edit'"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-row v-if="form.IsShow('add', 'reset')">
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item
|
||||||
|
name="password"
|
||||||
|
label="密码"
|
||||||
|
:rules="[
|
||||||
|
{ required: true },
|
||||||
|
{
|
||||||
|
validator: form.rules.checkPassword,
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<a-input-password
|
||||||
|
v-model:value="form.data.password"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
/>
|
||||||
|
<!-- <Progress
|
||||||
|
:percent="20"
|
||||||
|
:steps="5"
|
||||||
|
:strokeColor="{ from: '#ff5500', to: '#ff9300' }"
|
||||||
|
/> -->
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-row v-if="form.IsShow('add', 'reset')">
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item
|
||||||
|
name="confirmPassword"
|
||||||
|
label="确认密码"
|
||||||
|
:rules="[
|
||||||
|
{ required: true, message: '请输入8~64位的密码' },
|
||||||
|
{
|
||||||
|
validator: form.rules.checkAgainPassword,
|
||||||
|
trigger: 'change',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<a-input-password
|
||||||
|
v-model:value="form.data.confirmPassword"
|
||||||
|
placeholder="请再次输入密码"
|
||||||
|
:maxlength="64"
|
||||||
|
/>
|
||||||
|
<!-- <Progress
|
||||||
|
:percent="60"
|
||||||
|
:steps="5"
|
||||||
|
:strokeColor="{ from: '#ff5500', to: '#ff9300' }"
|
||||||
|
/> -->
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<!-- 还差页面权限 -->
|
||||||
|
<a-row :gutter="24" v-if="form.IsShow('add', 'edit')">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item name="roleIdList" label="角色" class="flex">
|
||||||
|
<a-select
|
||||||
|
v-model:value="form.data.roleIdList"
|
||||||
|
mode="multiple"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请选择角色"
|
||||||
|
:options="form.roleOptions"
|
||||||
|
></a-select>
|
||||||
|
|
||||||
|
<PermissionButton
|
||||||
|
:uhasPermission="`${rolePermission}:update`"
|
||||||
|
@click="form.clickAddItem('roleIdList', 'Role')"
|
||||||
|
class="add-item"
|
||||||
|
>
|
||||||
|
<AIcon type="PlusOutlined" />
|
||||||
|
</PermissionButton>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item name="orgIdList" label="组织" class="flex">
|
||||||
|
<a-tree-select
|
||||||
|
v-model:value="form.data.orgIdList"
|
||||||
|
show-search
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请选择组织"
|
||||||
|
multiple
|
||||||
|
:tree-data="form.departmentOptions"
|
||||||
|
:fieldNames="{ label: 'name', value: 'id' }"
|
||||||
|
>
|
||||||
|
<template #title="{ name }">
|
||||||
|
{{ name }}
|
||||||
|
</template>
|
||||||
|
</a-tree-select>
|
||||||
|
<PermissionButton
|
||||||
|
:uhasPermission="`${deptPermission}:update`"
|
||||||
|
@click="form.clickAddItem('roleIdList', 'Role')"
|
||||||
|
class="add-item"
|
||||||
|
>
|
||||||
|
<AIcon type="PlusOutlined" />
|
||||||
|
</PermissionButton>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-row :gutter="24" v-if="form.IsShow('add', 'edit')">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item
|
||||||
|
name="telephone"
|
||||||
|
label="手机号"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
pattern: /^1[3456789]\d{9}$/,
|
||||||
|
message: '请输入正确的手机号',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="form.data.telephone"
|
||||||
|
placeholder="请输入手机号"
|
||||||
|
:maxlength="64"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item
|
||||||
|
name="email"
|
||||||
|
label="邮箱"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
pattern:
|
||||||
|
/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/,
|
||||||
|
message: '请输入正确的邮箱',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="form.data.email"
|
||||||
|
placeholder="请输入邮箱"
|
||||||
|
:maxlength="64"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||||
|
import { FormInstance, message } from 'ant-design-vue';
|
||||||
|
// import Progress from './Progress.vue';
|
||||||
|
import {
|
||||||
|
validateField_api,
|
||||||
|
getRoleList_api,
|
||||||
|
getDepartmentList_api,
|
||||||
|
addUser_api,
|
||||||
|
updateUser_api,
|
||||||
|
updatePassword_api,
|
||||||
|
getUser_api,
|
||||||
|
} from '@/api/system/user';
|
||||||
|
import { Rule } from 'ant-design-vue/es/form';
|
||||||
|
import { DefaultOptionType } from 'ant-design-vue/es/vc-tree-select/TreeSelect';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
|
||||||
|
const deptPermission = 'system/Department';
|
||||||
|
const rolePermission = 'system/Role';
|
||||||
|
|
||||||
|
const emits = defineEmits(['confirm']);
|
||||||
|
// 弹窗相关
|
||||||
|
const dialog = reactive({
|
||||||
|
title: '',
|
||||||
|
visible: false,
|
||||||
|
type: '' as modalType,
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
handleOk: () => {
|
||||||
|
formRef.value?.validate().then(() => {
|
||||||
|
form.submit(() => {
|
||||||
|
dialog.changeVisible('', {} as any);
|
||||||
|
emits('confirm');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 设置表单类型
|
||||||
|
* @param type 弹窗类型
|
||||||
|
* @param defaultForm 表单回显对象
|
||||||
|
*/
|
||||||
|
changeVisible: (type: modalType, defaultForm: formType) => {
|
||||||
|
dialog.setTitle(type);
|
||||||
|
form.getUserInfo(defaultForm.id || '', type);
|
||||||
|
dialog.type = type;
|
||||||
|
dialog.visible = type !== '';
|
||||||
|
},
|
||||||
|
setTitle: (type: modalType) => {
|
||||||
|
if (type === 'add') dialog.title = '新增';
|
||||||
|
else if (type === 'edit') dialog.title = '编辑';
|
||||||
|
else if (type === 'reset') dialog.title = '重置密码';
|
||||||
|
else dialog.title = '';
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 将打开弹窗的操作暴露给父组件
|
||||||
|
defineExpose({
|
||||||
|
openDialog: dialog.changeVisible,
|
||||||
|
});
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const form = reactive({
|
||||||
|
data: {} as formType,
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
checkUserName: (_rule: Rule, value: string): Promise<any> =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
console.log(_rule);
|
||||||
|
if (dialog.type === 'edit') return resolve('');
|
||||||
|
|
||||||
|
if (!value) return reject('请输入用户名');
|
||||||
|
else if (value.length > 64) return reject('最多可输入64个字符');
|
||||||
|
validateField_api('username', value).then((resp: any): any => {
|
||||||
|
resp.result.passed
|
||||||
|
? resolve('')
|
||||||
|
: reject(resp.result.reason);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
checkPassword: (_rule: Rule, value: string): Promise<any> =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
if (!value) return reject('请输入8~64位的密码');
|
||||||
|
else if (value.length > 64) return reject('最多可输入64个字符');
|
||||||
|
else if (value.length < 8) return reject('密码不能少于8位');
|
||||||
|
validateField_api('password', value).then((resp: any) => {
|
||||||
|
resp.result.passed
|
||||||
|
? resolve('')
|
||||||
|
: reject(resp.result.reason);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
checkAgainPassword: (_rule: Rule, value: string): Promise<any> => {
|
||||||
|
if (!value) return Promise.reject('');
|
||||||
|
return value === form.data.password
|
||||||
|
? Promise.resolve()
|
||||||
|
: Promise.reject('两次密码输入不一致');
|
||||||
|
},
|
||||||
|
//
|
||||||
|
},
|
||||||
|
|
||||||
|
roleOptions: [] as optionType[],
|
||||||
|
departmentOptions: [] as DefaultOptionType[],
|
||||||
|
|
||||||
|
init: () => {
|
||||||
|
form.getDepartmentList();
|
||||||
|
form.getRoleList();
|
||||||
|
},
|
||||||
|
getUserInfo: (id: string, type: modalType) => {
|
||||||
|
if (type === 'add') form.data = {} as formType;
|
||||||
|
else if (type === 'reset') form.data = { id } as formType;
|
||||||
|
else if (type === 'edit') {
|
||||||
|
getUser_api(id).then((resp: any) => {
|
||||||
|
form.data = {
|
||||||
|
...(resp.result as formType),
|
||||||
|
orgIdList: resp.result.orgList.map(
|
||||||
|
(item: dictType) => item.id,
|
||||||
|
),
|
||||||
|
roleIdList: resp.result.roleList.map(
|
||||||
|
(item: dictType) => item.id,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
nextTick(() => {
|
||||||
|
formRef.value?.clearValidate();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submit: (cb?: Function) => {
|
||||||
|
let api: axiosFunType;
|
||||||
|
let params = {};
|
||||||
|
switch (dialog.type) {
|
||||||
|
case 'add': {
|
||||||
|
api = addUser_api;
|
||||||
|
params = {
|
||||||
|
user: form.data,
|
||||||
|
orgIdList: form.data.orgIdList,
|
||||||
|
roleIdList: form.data.roleIdList,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'edit': {
|
||||||
|
api = updateUser_api;
|
||||||
|
params = {
|
||||||
|
id: form.data.id,
|
||||||
|
user: form.data,
|
||||||
|
orgIdList: form.data.orgIdList,
|
||||||
|
roleIdList: form.data.roleIdList,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'reset': {
|
||||||
|
api = updatePassword_api;
|
||||||
|
params = {
|
||||||
|
id: form.data.id,
|
||||||
|
password: form.data.password,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(params);
|
||||||
|
|
||||||
|
api(params).then(() => {
|
||||||
|
message.success('操作成功');
|
||||||
|
cb && cb();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getRoleList: () => {
|
||||||
|
getRoleList_api().then((resp: any) => {
|
||||||
|
form.roleOptions = resp.result.map((item: dictType) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getDepartmentList: () => {
|
||||||
|
getDepartmentList_api().then((resp: any) => {
|
||||||
|
form.departmentOptions = resp.result;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
IsShow: (...typeList: modalType[]) => typeList.includes(dialog.type),
|
||||||
|
clickAddItem: (prop: 'roleIdList' | 'orgIdList', target: string) => {
|
||||||
|
const tab: any = window.open(`${origin}/#/system/${target}?save=true`);
|
||||||
|
tab.onSaveSuccess = (value: string) => {
|
||||||
|
form.data[prop] = [...(form.data[prop] || []), value];
|
||||||
|
if (prop === 'roleIdList') form.getRoleList();
|
||||||
|
else form.getDepartmentList();
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
form.init();
|
||||||
|
|
||||||
|
interface AxiosResponseRewrite<T = any[]> extends AxiosResponse<T, any> {
|
||||||
|
result: T;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
|
type axiosFunType = (data: any) => Promise<AxiosResponseRewrite<unknown>>;
|
||||||
|
type modalType = '' | 'add' | 'edit' | 'reset';
|
||||||
|
type formType = {
|
||||||
|
id?: string;
|
||||||
|
name: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
confirmPassword: string;
|
||||||
|
roleIdList: string[];
|
||||||
|
orgIdList: string[];
|
||||||
|
telephone: string;
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
type dictType = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
children?: dictType;
|
||||||
|
};
|
||||||
|
type optionType = {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.edit-dialog-container {
|
||||||
|
.ant-form-item {
|
||||||
|
&.flex {
|
||||||
|
:deep(.ant-form-item-control-input-content) {
|
||||||
|
display: flex;
|
||||||
|
.ant-select {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.ant-btn {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: 1px solid #1d39c4;
|
||||||
|
color: #1d39c4;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,76 @@
|
||||||
|
<template>
|
||||||
|
<div class="progress-container">
|
||||||
|
<div class="value" :style="valueStyle"></div>
|
||||||
|
<div
|
||||||
|
class="split"
|
||||||
|
v-for="leftValue in valueArr"
|
||||||
|
:style="{ left: leftValue + '%' }"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
steps?: number;
|
||||||
|
strokeColor?:
|
||||||
|
| {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
}
|
||||||
|
| string;
|
||||||
|
percent: number;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
steps: 1,
|
||||||
|
strokeColor: '#108ee9',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const valueStyle = computed(() => {
|
||||||
|
let background = '';
|
||||||
|
if (typeof props.strokeColor === 'string') {
|
||||||
|
background = props.strokeColor;
|
||||||
|
} else {
|
||||||
|
background = `-webkit-linear-gradient(
|
||||||
|
left,
|
||||||
|
${props.strokeColor.from},
|
||||||
|
${props.strokeColor.to}
|
||||||
|
)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
background,
|
||||||
|
'clip-path': `polygon(0px 0px, ${props.percent}% 0px, ${props.percent}% 100%, 0px 100%)`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const valueArr = computed(() => {
|
||||||
|
const result = [];
|
||||||
|
for (let i = 1; i < props.steps; i++) result.push((100 / props.steps) * i);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.progress-container {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
height: 8px;
|
||||||
|
margin: 3px 0;
|
||||||
|
|
||||||
|
.split {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 1px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
transition: all 0.35s ease-in-out 0s;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,369 @@
|
||||||
|
<template>
|
||||||
|
<div class="user-container">
|
||||||
|
<Search :columns="query.columns" @search="query.search" />
|
||||||
|
|
||||||
|
<JTable
|
||||||
|
ref="tableRef"
|
||||||
|
:columns="table.columns"
|
||||||
|
:request="getUserList_api"
|
||||||
|
model="TABLE"
|
||||||
|
:params="query.params.value"
|
||||||
|
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
|
||||||
|
>
|
||||||
|
<template #headerTitle>
|
||||||
|
<!-- <a-button
|
||||||
|
type="primary"
|
||||||
|
@click="table.openDialog('add')"
|
||||||
|
style="margin-right: 10px"
|
||||||
|
><AIcon type="PlusOutlined" />新增</a-button
|
||||||
|
> -->
|
||||||
|
<PermissionButton
|
||||||
|
:uhasPermission="`${permission}:add`"
|
||||||
|
type="primary"
|
||||||
|
@click="table.openDialog('add')"
|
||||||
|
>
|
||||||
|
<AIcon type="PlusOutlined" />新增
|
||||||
|
</PermissionButton>
|
||||||
|
</template>
|
||||||
|
<template #type="slotProps">
|
||||||
|
{{ slotProps.type.name }}
|
||||||
|
</template>
|
||||||
|
<template #status="slotProps">
|
||||||
|
<BadgeStatus
|
||||||
|
:status="slotProps.status"
|
||||||
|
:text="slotProps.status ? '正常' : '禁用'"
|
||||||
|
:statusNames="{
|
||||||
|
1: 'success',
|
||||||
|
0: 'error',
|
||||||
|
}"
|
||||||
|
></BadgeStatus>
|
||||||
|
</template>
|
||||||
|
<template #action="slotProps">
|
||||||
|
<a-space :size="16">
|
||||||
|
<!-- <a-tooltip>
|
||||||
|
<template #title>编辑</template>
|
||||||
|
<a-button
|
||||||
|
style="padding: 0"
|
||||||
|
type="link"
|
||||||
|
@click="table.openDialog('edit', slotProps)"
|
||||||
|
>
|
||||||
|
<AIcon type="EditOutlined" />
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip> -->
|
||||||
|
<!-- <a-popconfirm
|
||||||
|
:title="`确定${slotProps.status ? '禁用' : '启用'}吗?`"
|
||||||
|
ok-text="确定"
|
||||||
|
cancel-text="取消"
|
||||||
|
@confirm="table.changeStatus(slotProps)"
|
||||||
|
>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{
|
||||||
|
slotProps.status ? '禁用' : '启用'
|
||||||
|
}}</template>
|
||||||
|
<a-button style="padding: 0" type="link">
|
||||||
|
<stop-outlined v-if="slotProps.status" />
|
||||||
|
<play-circle-outlined v-else />
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-popconfirm> -->
|
||||||
|
<!-- <a-tooltip>
|
||||||
|
<template #title>重置密码</template>
|
||||||
|
<a-button
|
||||||
|
style="padding: 0"
|
||||||
|
type="link"
|
||||||
|
@click="table.openDialog('reset', slotProps)"
|
||||||
|
>
|
||||||
|
<AIcon type="icon-zhongzhimima" />
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip> -->
|
||||||
|
<!-- <a-popconfirm
|
||||||
|
title="确认删除"
|
||||||
|
ok-text="确定"
|
||||||
|
cancel-text="取消"
|
||||||
|
@confirm="table.clickDel(slotProps)"
|
||||||
|
:disabled="slotProps.status"
|
||||||
|
>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{
|
||||||
|
slotProps.status ? '请先禁用,再删除' : '删除'
|
||||||
|
}}</template>
|
||||||
|
<a-button
|
||||||
|
style="padding: 0"
|
||||||
|
type="link"
|
||||||
|
:disabled="slotProps.status"
|
||||||
|
>
|
||||||
|
<AIcon type="DeleteOutlined" />
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-popconfirm> -->
|
||||||
|
|
||||||
|
<PermissionButton
|
||||||
|
:uhasPermission="`${permission}:update`"
|
||||||
|
type="link"
|
||||||
|
:tooltip="{
|
||||||
|
title: '编辑',
|
||||||
|
}"
|
||||||
|
@click="table.openDialog('edit')"
|
||||||
|
>
|
||||||
|
<AIcon type="EditOutlined" />
|
||||||
|
</PermissionButton>
|
||||||
|
<PermissionButton
|
||||||
|
:uhasPermission="`${permission}:action`"
|
||||||
|
type="link"
|
||||||
|
:tooltip="{
|
||||||
|
title: `${slotProps.status ? '禁用' : '启用'}`,
|
||||||
|
}"
|
||||||
|
:popConfirm="{
|
||||||
|
title: `确定${
|
||||||
|
slotProps.status ? '禁用' : '启用'
|
||||||
|
}吗?`,
|
||||||
|
onConfirm: () => table.changeStatus(slotProps),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<stop-outlined v-if="slotProps.status" />
|
||||||
|
<play-circle-outlined v-else />
|
||||||
|
</PermissionButton>
|
||||||
|
<PermissionButton
|
||||||
|
:uhasPermission="`${permission}:update`"
|
||||||
|
type="link"
|
||||||
|
:tooltip="{
|
||||||
|
title: '重置密码',
|
||||||
|
}"
|
||||||
|
@click="table.openDialog('reset', slotProps)"
|
||||||
|
>
|
||||||
|
<AIcon type="icon-zhongzhimima" />
|
||||||
|
</PermissionButton>
|
||||||
|
<PermissionButton
|
||||||
|
type="link"
|
||||||
|
:uhasPermission="`${permission}:delete`"
|
||||||
|
:tooltip="{
|
||||||
|
title: slotProps.status
|
||||||
|
? '请先禁用,再删除'
|
||||||
|
: '删除',
|
||||||
|
}"
|
||||||
|
:popConfirm="{
|
||||||
|
title: `确认删除`,
|
||||||
|
onConfirm: () => table.clickDel(slotProps),
|
||||||
|
}"
|
||||||
|
:disabled="slotProps.status"
|
||||||
|
>
|
||||||
|
<AIcon type="DeleteOutlined" />
|
||||||
|
</PermissionButton>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</JTable>
|
||||||
|
|
||||||
|
<div class="dialogs">
|
||||||
|
<EditUserDialog ref="editDialogRef" @confirm="table.refresh" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="UserMange">
|
||||||
|
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||||
|
import EditUserDialog from './components/EditUserDialog.vue';
|
||||||
|
import {
|
||||||
|
getUserType_api,
|
||||||
|
getUserList_api,
|
||||||
|
changeUserStatus_api,
|
||||||
|
deleteUser_api,
|
||||||
|
} from '@/api/system/user';
|
||||||
|
import { StopOutlined, PlayCircleOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const permission = 'system/User';
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
ellipsis: true,
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户名',
|
||||||
|
dataIndex: 'username',
|
||||||
|
key: 'username',
|
||||||
|
ellipsis: true,
|
||||||
|
fixed: 'left',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户类型',
|
||||||
|
dataIndex: 'type',
|
||||||
|
key: 'type',
|
||||||
|
ellipsis: true,
|
||||||
|
fixed: 'left',
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: () =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
getUserType_api().then((resp: any) => {
|
||||||
|
resolve(
|
||||||
|
resp.result.map((item: dictType) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
ellipsis: true,
|
||||||
|
search: {
|
||||||
|
rename: 'status',
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: '启用',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '禁用',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '手机号',
|
||||||
|
dataIndex: 'telephone',
|
||||||
|
key: 'telephone',
|
||||||
|
ellipsis: true,
|
||||||
|
fixed: 'left',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '邮箱',
|
||||||
|
dataIndex: 'email',
|
||||||
|
key: 'email',
|
||||||
|
ellipsis: true,
|
||||||
|
fixed: 'left',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
params: ref({}),
|
||||||
|
|
||||||
|
search: (params: object) => {
|
||||||
|
query.params.value = params;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const editDialogRef = ref(); // 弹窗实例
|
||||||
|
const tableRef = ref<Record<string, any>>({}); // 表格实例
|
||||||
|
const table = {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户名',
|
||||||
|
dataIndex: 'username',
|
||||||
|
key: 'username',
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户类型',
|
||||||
|
dataIndex: 'type',
|
||||||
|
key: 'type',
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '手机号',
|
||||||
|
dataIndex: 'telephone',
|
||||||
|
key: 'telephone',
|
||||||
|
ellipsis: true,
|
||||||
|
fixed: 'left',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '邮箱',
|
||||||
|
dataIndex: 'email',
|
||||||
|
key: 'email',
|
||||||
|
ellipsis: true,
|
||||||
|
fixed: 'left',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'action',
|
||||||
|
key: 'action',
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
// 打开编辑弹窗
|
||||||
|
openDialog: (type: modalType, row?: any) => {
|
||||||
|
editDialogRef.value.openDialog(type, row || {});
|
||||||
|
},
|
||||||
|
changeStatus: (row: any) => {
|
||||||
|
const params = {
|
||||||
|
id: row.id,
|
||||||
|
status: row.status === 0 ? 1 : 0,
|
||||||
|
};
|
||||||
|
changeUserStatus_api(params).then(() => {
|
||||||
|
message.success('操作成功');
|
||||||
|
table.refresh();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 删除
|
||||||
|
clickDel: (row: any) => {
|
||||||
|
deleteUser_api(row.id).then(() => {
|
||||||
|
message.success('操作成功');
|
||||||
|
table.refresh();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 刷新列表
|
||||||
|
refresh: () => {
|
||||||
|
tableRef.value.reload();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type dictType = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
type modalType = '' | 'add' | 'edit' | 'reset';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.user-container {
|
||||||
|
padding: 24px;
|
||||||
|
|
||||||
|
:deep(.ant-table-tbody) {
|
||||||
|
.ant-table-cell {
|
||||||
|
.ant-space-item {
|
||||||
|
.ant-btn-link {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue