Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev
This commit is contained in:
commit
b55a7bd490
|
@ -0,0 +1,32 @@
|
||||||
|
import server from '@/utils/request';
|
||||||
|
|
||||||
|
export const queryNetworkConfig = (params: object) =>
|
||||||
|
server.post(`/network/config/_query`, params);
|
||||||
|
|
||||||
|
export const remove = (id: string) => server.remove(`/network/config/${id}`);
|
||||||
|
|
||||||
|
export const shutdown = (data: object) =>
|
||||||
|
server.post(`/network/config/${data}/_shutdown`);
|
||||||
|
|
||||||
|
export const start = (data: object) =>
|
||||||
|
server.post(`/network/config/${data}/_start`);
|
||||||
|
|
||||||
|
export const supports = () => server.get(`/network/config/supports`);
|
||||||
|
|
||||||
|
export const query = (data: Object) =>
|
||||||
|
server.post(`/network/config/_query`, data);
|
||||||
|
|
||||||
|
export const providers = () => server.get(`/gateway/device/providers`);
|
||||||
|
|
||||||
|
export const resourcesCurrent = () =>
|
||||||
|
server.get(`/network/resources/alive/_current`);
|
||||||
|
|
||||||
|
export const resourceClusters = () => server.get(`network/resources/clusters`);
|
||||||
|
|
||||||
|
export const resourceClustersById = (id: string) =>
|
||||||
|
server.get(`/network/resources/alive/${id}`);
|
||||||
|
|
||||||
|
export const allResources = () => server.get(`/network/resources/alive/_all`);
|
||||||
|
|
||||||
|
export const certificates = () =>
|
||||||
|
server.get(`/network/certificate/_query/no-paging?paging=false`);
|
|
@ -0,0 +1,67 @@
|
||||||
|
import server from '@/utils/request'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询数据
|
||||||
|
* @param data 分页搜索数据
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const query = (data: Record<string, any>) => server.post('/device/aliyun/bridge/_query', data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询产品列表
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const queryProductList = (data?: Record<string, any>) => server.post('/device-product/_query/no-paging', data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存数据
|
||||||
|
* @param data 阿里云
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const savePatch = (data: Record<string, any>) => server.patch(`/device/aliyun/bridge`, data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据阿里云ID获取阿里云详情
|
||||||
|
* @param id 阿里云ID
|
||||||
|
* @returns 阿里云详情
|
||||||
|
*/
|
||||||
|
export const detail = (id: string) => server.get(`/device/aliyun/bridge/${id}`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除阿里云
|
||||||
|
* @param id 阿里云ID
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const _delete = (id: string) => server.remove(`/device/aliyun/bridge/${id}`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用阿里云
|
||||||
|
* @param id 阿里云ID
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const _deploy = (id: string) => server.post(`/device/aliyun/bridge/${id}/enable`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁用阿里云
|
||||||
|
* @param id 阿里云ID
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const _undeploy = (id: string) => server.post(`/device/aliyun/bridge/${id}/disable`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取服务地址的下拉列表
|
||||||
|
* @param params
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getRegionsList = (params?: Record<string, any>) => server.get(`/device/aliyun/bridge/regions`, params)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品映射中的阿里云产品下拉列表
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getAliyunProductsList = (data?: Record<string, any>) => server.post(`/device/aliyun/bridge/products/_query`, data)
|
||||||
|
|
|
@ -5,4 +5,67 @@ import server from '@/utils/request'
|
||||||
* @param data 分页搜索数据
|
* @param data 分页搜索数据
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const query = (data: Record<string, any>) => server.post('/dueros/product/_query', data)
|
export const query = (data: Record<string, any>) => server.post('/dueros/product/_query', data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询产品列表
|
||||||
|
* @param id
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const queryProductList = (id?: string) => server.post('/device-product/_query/no-paging', {
|
||||||
|
paging: false,
|
||||||
|
terms: id ? [{
|
||||||
|
column: 'id$dueros-product$not',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{ column: 'id', type: 'or', value: id }
|
||||||
|
] : [{
|
||||||
|
column: 'id$dueros-product$not',
|
||||||
|
value: 1,
|
||||||
|
}],
|
||||||
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询类型
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const queryTypes = () => server.get('/dueros/product/types')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存数据
|
||||||
|
* @param data dueros
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const savePatch = (data: Record<string, any>) => server.patch(`/dueros/product`, data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据duerosID获取dueros详情
|
||||||
|
* @param id duerosID
|
||||||
|
* @returns dueros详情
|
||||||
|
*/
|
||||||
|
export const detail = (id: string) => server.get(`/dueros/product/${id}`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除dueros
|
||||||
|
* @param id duerosID
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const _delete = (id: string) => server.remove(`/dueros/product/${id}`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用dueros
|
||||||
|
* @param id duerosID
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const _deploy = (id: string) => server.post(`/dueros/product/${id}/_enable`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁用dueros
|
||||||
|
* @param id duerosID
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const _undeploy = (id: string) => server.post(`/dueros/product/${id}/_disable`)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import server from '@/utils/request'
|
||||||
|
/**
|
||||||
|
* 查询规则编排分页列表
|
||||||
|
*/
|
||||||
|
export const queryList = (data: any) => server.post('/rule-engine/instance/_query', data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增规则
|
||||||
|
*/
|
||||||
|
export const saveRule = (data: any) => server.post('/rule-editor/flows/_create',data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改规则
|
||||||
|
*/
|
||||||
|
export const modify = (id:any ,data:any) => server.put(`/rule-engine/instance/${id}`,data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动规则
|
||||||
|
*/
|
||||||
|
export const startRule = (id:string) => server.post(`/rule-engine/instance/${id}/_start`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁用规则
|
||||||
|
*/
|
||||||
|
export const stopRule = (id:string) => server.post(`/rule-engine/instance/${id}/_stop`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除规则
|
||||||
|
*/
|
||||||
|
export const deleteRule = (id:string) => server.remove(`/rule-engine/instance/${id}`)
|
|
@ -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`);
|
||||||
|
|
||||||
|
@ -24,4 +26,13 @@ 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}`);
|
|
@ -1,79 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="chart" ref="chart"></div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import * as echarts from 'echarts';
|
|
||||||
|
|
||||||
const { proxy } = <any>getCurrentInstance();
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
// 图表数据
|
|
||||||
chartYData: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
chartXData: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制图表
|
|
||||||
*/
|
|
||||||
const createChart = () => {
|
|
||||||
nextTick(() => {
|
|
||||||
const myChart = echarts.init(proxy.$refs.chart);
|
|
||||||
const options = {
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: props.chartXData,
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value',
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
top: '5%',
|
|
||||||
bottom: 0,
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
axisPointer: {
|
|
||||||
type: 'shadow',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '在线数',
|
|
||||||
data: props.chartYData.reverse(),
|
|
||||||
type: 'bar',
|
|
||||||
showBackground: true,
|
|
||||||
itemStyle: {
|
|
||||||
color: '#D3ADF7',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
myChart.setOption(options);
|
|
||||||
window.addEventListener('resize', function () {
|
|
||||||
myChart.resize();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.chartYData,
|
|
||||||
() => createChart(),
|
|
||||||
{ immediate: true, deep: true },
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less">
|
|
||||||
.chart {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -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>
|
|
@ -1,99 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="chart" ref="chart"></div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import * as echarts from 'echarts';
|
|
||||||
|
|
||||||
const { proxy } = <any>getCurrentInstance();
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
// 图表数据
|
|
||||||
chartYData: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
chartXData: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制图表
|
|
||||||
*/
|
|
||||||
const createChart = () => {
|
|
||||||
nextTick(() => {
|
|
||||||
const myChart = echarts.init(proxy.$refs.chart);
|
|
||||||
const options = {
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
axisPointer: {
|
|
||||||
type: 'shadow',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
boundaryGap: false,
|
|
||||||
show: false,
|
|
||||||
data:props.chartXData
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value',
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
top: '2%',
|
|
||||||
bottom: 0,
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '消息量',
|
|
||||||
data: props.chartYData,
|
|
||||||
type: 'line',
|
|
||||||
smooth: true, // 是否平滑曲线
|
|
||||||
symbolSize: 0, // 拐点大小
|
|
||||||
color: '#F29B55',
|
|
||||||
areaStyle: {
|
|
||||||
color: {
|
|
||||||
type: 'linear',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
x2: 0,
|
|
||||||
y2: 1,
|
|
||||||
colorStops: [
|
|
||||||
{
|
|
||||||
offset: 0,
|
|
||||||
color: '#FBBB87', // 100% 处的颜色
|
|
||||||
},
|
|
||||||
{
|
|
||||||
offset: 1,
|
|
||||||
color: '#FFFFFF', // 0% 处的颜色
|
|
||||||
},
|
|
||||||
],
|
|
||||||
global: false, // 缺省为 false
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
myChart.setOption(options);
|
|
||||||
window.addEventListener('resize', function () {
|
|
||||||
myChart.resize();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.chartYData,
|
|
||||||
() => createChart(),
|
|
||||||
{ immediate: true, deep: true },
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less">
|
|
||||||
.chart {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,112 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="chart" ref="chart"></div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import * as echarts from 'echarts';
|
|
||||||
|
|
||||||
const { proxy } = <any>getCurrentInstance();
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
// 图表数据
|
|
||||||
x: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
maxY:{
|
|
||||||
type:Number,
|
|
||||||
default: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制图表
|
|
||||||
*/
|
|
||||||
const createChart = () => {
|
|
||||||
nextTick(() => {
|
|
||||||
const myChart = echarts.init(proxy.$refs.chart);
|
|
||||||
const options = {
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
boundaryGap: false,
|
|
||||||
data: props.x,
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value',
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
formatter: '{b0}<br />{a0}: {c0}',
|
|
||||||
// formatter: '{b0}<br />{a0}: {c0}<br />{a1}: {c1}%'
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
top: '2%',
|
|
||||||
bottom: '5%',
|
|
||||||
left: props.maxY > 100000 ? '90px' : '50px',
|
|
||||||
right: '50px',
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '消息量',
|
|
||||||
data: props.y,
|
|
||||||
type: 'bar',
|
|
||||||
// type: 'line',
|
|
||||||
// smooth: true,
|
|
||||||
color: '#597EF7',
|
|
||||||
barWidth: '30%',
|
|
||||||
// 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
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '占比',
|
|
||||||
data: props.y,
|
|
||||||
// data: percentageY,
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
symbolSize: 0, // 拐点大小
|
|
||||||
color: '#96ECE3',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
myChart.setOption(options);
|
|
||||||
window.addEventListener('resize', function () {
|
|
||||||
myChart.resize();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.y,
|
|
||||||
() => createChart(),
|
|
||||||
{ immediate: true, deep: true },
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less">
|
|
||||||
.chart {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -72,7 +72,7 @@ const props = defineProps({
|
||||||
}
|
}
|
||||||
.content-right {
|
.content-right {
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 100%;
|
height: 123px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-grow: .7;
|
flex-grow: .7;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
|
|
|
@ -24,10 +24,12 @@
|
||||||
:footer="onlineFooter"
|
:footer="onlineFooter"
|
||||||
:value="onlineToday"
|
:value="onlineToday"
|
||||||
>
|
>
|
||||||
<BarChart
|
<!-- <BarChart
|
||||||
:chartXData="barChartXData"
|
:chartXData="barChartXData"
|
||||||
:chartYData="barChartYData"
|
:chartYData="barChartYData"
|
||||||
></BarChart> </TopCard
|
></BarChart> -->
|
||||||
|
<Charts :options="onlineOptions"></Charts>
|
||||||
|
</TopCard
|
||||||
></a-col>
|
></a-col>
|
||||||
<a-col :span="6"
|
<a-col :span="6"
|
||||||
><TopCard
|
><TopCard
|
||||||
|
@ -35,10 +37,7 @@
|
||||||
:footer="messageFooter"
|
:footer="messageFooter"
|
||||||
:value="dayMessage"
|
:value="dayMessage"
|
||||||
>
|
>
|
||||||
<LineChart
|
<Charts :options="TodayDevOptions"></Charts> </TopCard
|
||||||
:chartXData="lineChartXData"
|
|
||||||
:chartYData="lineChartYData"
|
|
||||||
></LineChart> </TopCard
|
|
||||||
></a-col>
|
></a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row :span="24">
|
<a-row :span="24">
|
||||||
|
@ -55,7 +54,7 @@
|
||||||
</template>
|
</template>
|
||||||
</Guide>
|
</Guide>
|
||||||
<div class="message-chart">
|
<div class="message-chart">
|
||||||
<MessageChart :x="messageChartXData" :y="messageChartYData" :maxY="messageMaxChartYData"></MessageChart>
|
<Charts :options="devMegOptions"></Charts>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
@ -74,11 +73,9 @@
|
||||||
</page-container>
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import BarChart from './components/BarChart.vue';
|
|
||||||
import LineChart from './components/LineChart.vue';
|
|
||||||
import TimeSelect from './components/TimeSelect.vue';
|
import TimeSelect from './components/TimeSelect.vue';
|
||||||
|
import Charts from './components/Charts.vue'
|
||||||
import Guide from './components/Guide.vue';
|
import Guide from './components/Guide.vue';
|
||||||
import MessageChart from './components/messageChart.vue';
|
|
||||||
import {
|
import {
|
||||||
productCount,
|
productCount,
|
||||||
deviceCount,
|
deviceCount,
|
||||||
|
@ -130,13 +127,12 @@ let messageFooter = ref<Footer[]>([
|
||||||
value: 0,
|
value: 0,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
let lineChartYData = ref<any[]>([]);
|
|
||||||
let lineChartXData = ref<any[]>([]);
|
|
||||||
let barChartXData = ref<any[]>([]);
|
|
||||||
let barChartYData = ref<any[]>([]);
|
|
||||||
let messageChartXData = ref<any[]>([]);
|
let messageChartXData = ref<any[]>([]);
|
||||||
let messageChartYData = ref<any[]>([]);
|
let messageChartYData = ref<any[]>([]);
|
||||||
let messageMaxChartYData = ref<number>();
|
let messageMaxChartYData = ref<number>();
|
||||||
|
let onlineOptions = ref<any>({});
|
||||||
|
let TodayDevOptions = ref<any>({});
|
||||||
|
let devMegOptions = ref<any>({});
|
||||||
const quickBtnList = [
|
const quickBtnList = [
|
||||||
{ label: '昨日', value: 'yesterday' },
|
{ label: '昨日', value: 'yesterday' },
|
||||||
{ label: '近一周', value: 'week' },
|
{ label: '近一周', value: 'week' },
|
||||||
|
@ -215,13 +211,165 @@ const getOnline = () => {
|
||||||
const x = res.result
|
const x = res.result
|
||||||
.map((item: any) => item.data.timeString)
|
.map((item: any) => item.data.timeString)
|
||||||
.reverse();
|
.reverse();
|
||||||
barChartXData.value = x;
|
|
||||||
const y = res.result.map((item: any) => item.data.value);
|
const y = res.result.map((item: any) => item.data.value);
|
||||||
barChartYData.value = y;
|
const onlineYdata = y;
|
||||||
|
onlineYdata.reverse()
|
||||||
|
setOnlineChartOpition(x,onlineYdata);
|
||||||
deviceFooter.value[0].value = y?.[1];
|
deviceFooter.value[0].value = y?.[1];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
const setOnlineChartOpition = (x:Array<any>,y:Array<number>):void=>{
|
||||||
|
onlineOptions.value = {
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: x,
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: '5%',
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '在线数',
|
||||||
|
data: y,
|
||||||
|
type: 'bar',
|
||||||
|
showBackground: true,
|
||||||
|
itemStyle: {
|
||||||
|
color: '#D3ADF7',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const setTodayDevChartOption = (x:Array<any>,y:Array<number>):void =>{
|
||||||
|
TodayDevOptions = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
show: false,
|
||||||
|
data:x
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: '2%',
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '消息量',
|
||||||
|
data: y,
|
||||||
|
type: 'line',
|
||||||
|
smooth: true, // 是否平滑曲线
|
||||||
|
symbolSize: 0, // 拐点大小
|
||||||
|
color: '#F29B55',
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: '#FBBB87', // 100% 处的颜色
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: '#FFFFFF', // 0% 处的颜色
|
||||||
|
},
|
||||||
|
],
|
||||||
|
global: false, // 缺省为 false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const setDevMesChartOption = (x:Array<any>,y:Array<number>,maxY:number):void =>{
|
||||||
|
devMegOptions.value = {
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: x,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
formatter: '{b0}<br />{a0}: {c0}',
|
||||||
|
// formatter: '{b0}<br />{a0}: {c0}<br />{a1}: {c1}%'
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: '2%',
|
||||||
|
bottom: '5%',
|
||||||
|
left: maxY > 100000 ? '90px' : '50px',
|
||||||
|
right: '50px',
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '消息量',
|
||||||
|
data: y,
|
||||||
|
type: 'bar',
|
||||||
|
// type: 'line',
|
||||||
|
// smooth: true,
|
||||||
|
color: '#597EF7',
|
||||||
|
barWidth: '30%',
|
||||||
|
// 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
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '占比',
|
||||||
|
data: y,
|
||||||
|
// data: percentageY,
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbolSize: 0, // 拐点大小
|
||||||
|
color: '#96ECE3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
getOnline();
|
getOnline();
|
||||||
//今日设备消息量
|
//今日设备消息量
|
||||||
const getDevice = () => {
|
const getDevice = () => {
|
||||||
|
@ -279,8 +427,7 @@ const getDevice = () => {
|
||||||
);
|
);
|
||||||
const x = today.map((item: any) => item.data.timeString).reverse();
|
const x = today.map((item: any) => item.data.timeString).reverse();
|
||||||
const y = today.map((item: any) => item.data.value).reverse();
|
const y = today.map((item: any) => item.data.value).reverse();
|
||||||
lineChartXData.value = x;
|
setTodayDevChartOption(x,y);
|
||||||
lineChartYData.value = y;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -322,15 +469,16 @@ const getEcharts = (data: any) => {
|
||||||
},
|
},
|
||||||
]).then((res:any) => {
|
]).then((res:any) => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
messageChartXData.value = res.result
|
const x = res.result
|
||||||
.map((item: any) =>
|
.map((item: any) =>
|
||||||
_time === '1h'
|
_time === '1h'
|
||||||
? `${item.data.timeString}时`
|
? `${item.data.timeString}时`
|
||||||
: item.data.timeString,
|
: item.data.timeString,
|
||||||
)
|
)
|
||||||
.reverse();
|
.reverse();
|
||||||
messageChartYData.value = res.result.map((item: any) => item.data.value).reverse();
|
const y = res.result.map((item: any) => item.data.value).reverse();
|
||||||
messageMaxChartYData.value = Math.max.apply(null, messageChartYData.value.length ? messageChartYData.value : [0]);
|
const maxY = Math.max.apply(null, messageChartYData.value.length ? messageChartYData.value : [0]);
|
||||||
|
setDevMesChartOption(x,y,maxY);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,9 +16,6 @@
|
||||||
</a-button>
|
</a-button>
|
||||||
</template>
|
</template>
|
||||||
</JTable>
|
</JTable>
|
||||||
<a-button type="link" @click="detail(slotProps)">
|
|
||||||
<AIcon type="SearchOutlined" />
|
|
||||||
</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
|
@ -281,7 +281,7 @@ const api = ref<string>('');
|
||||||
const type = ref<string>('');
|
const type = ref<string>('');
|
||||||
|
|
||||||
const statusMap = new Map();
|
const statusMap = new Map();
|
||||||
statusMap.set('online', 'processing');
|
statusMap.set('online', 'success');
|
||||||
statusMap.set('offline', 'error');
|
statusMap.set('offline', 'error');
|
||||||
statusMap.set('notActive', 'warning');
|
statusMap.set('notActive', 'warning');
|
||||||
|
|
||||||
|
|
|
@ -1,163 +1,170 @@
|
||||||
<template>
|
<template>
|
||||||
<a-card class="device-product">
|
<page-container>
|
||||||
<Search
|
<a-card class="device-product">
|
||||||
:columns="query.columns"
|
<Search
|
||||||
target="product-manage"
|
:columns="query.columns"
|
||||||
@search="handleSearch"
|
target="product-manage"
|
||||||
/>
|
@search="handleSearch"
|
||||||
<JTable
|
/>
|
||||||
:columns="columns"
|
<JTable
|
||||||
:request="queryProductList"
|
:columns="columns"
|
||||||
ref="tableRef"
|
:request="queryProductList"
|
||||||
:defaultParams="{
|
ref="tableRef"
|
||||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
:defaultParams="{
|
||||||
}"
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
:params="params"
|
}"
|
||||||
>
|
:params="params"
|
||||||
<template #headerTitle>
|
>
|
||||||
<a-space>
|
<template #headerTitle>
|
||||||
<a-button type="primary" @click="add"
|
<a-space>
|
||||||
><plus-outlined />新增</a-button
|
<a-button type="primary" @click="add"
|
||||||
>
|
><plus-outlined />新增</a-button
|
||||||
<a-upload
|
|
||||||
name="file"
|
|
||||||
accept=".json"
|
|
||||||
:showUploadList="false"
|
|
||||||
:before-upload="beforeUpload"
|
|
||||||
>
|
|
||||||
<a-button>导入</a-button>
|
|
||||||
</a-upload>
|
|
||||||
</a-space>
|
|
||||||
</template>
|
|
||||||
<template #deviceType="slotProps">
|
|
||||||
<div>{{ slotProps.deviceType.text }}</div>
|
|
||||||
</template>
|
|
||||||
<template #card="slotProps">
|
|
||||||
<CardBox
|
|
||||||
:value="slotProps"
|
|
||||||
@click="handleClick"
|
|
||||||
:actions="getActions(slotProps, 'card')"
|
|
||||||
v-bind="slotProps"
|
|
||||||
:active="_selectedRowKeys.includes(slotProps.id)"
|
|
||||||
:status="slotProps.state"
|
|
||||||
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
|
||||||
:statusNames="{
|
|
||||||
1: 'success',
|
|
||||||
0: 'error',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #img>
|
|
||||||
<slot name="img">
|
|
||||||
<img :src="getImage('/device-product.png')" />
|
|
||||||
</slot>
|
|
||||||
</template>
|
|
||||||
<template #content>
|
|
||||||
<h3
|
|
||||||
@click.stop="handleView(slotProps.id)"
|
|
||||||
style="font-weight: 600"
|
|
||||||
>
|
>
|
||||||
{{ slotProps.name }}
|
<a-upload
|
||||||
</h3>
|
name="file"
|
||||||
<a-row>
|
accept=".json"
|
||||||
<a-col :span="12">
|
:showUploadList="false"
|
||||||
<div class="card-item-content-text">
|
:before-upload="beforeUpload"
|
||||||
设备类型
|
>
|
||||||
</div>
|
<a-button>导入</a-button>
|
||||||
<div>直连设备</div>
|
</a-upload>
|
||||||
</a-col>
|
</a-space>
|
||||||
</a-row>
|
</template>
|
||||||
</template>
|
<template #deviceType="slotProps">
|
||||||
<template #actions="item">
|
<div>{{ slotProps.deviceType.text }}</div>
|
||||||
|
</template>
|
||||||
|
<template #card="slotProps">
|
||||||
|
<CardBox
|
||||||
|
:value="slotProps"
|
||||||
|
@click="handleClick"
|
||||||
|
:actions="getActions(slotProps, 'card')"
|
||||||
|
v-bind="slotProps"
|
||||||
|
:active="_selectedRowKeys.includes(slotProps.id)"
|
||||||
|
:status="slotProps.state"
|
||||||
|
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
||||||
|
:statusNames="{
|
||||||
|
1: 'success',
|
||||||
|
0: 'error',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #img>
|
||||||
|
<slot name="img">
|
||||||
|
<img :src="getImage('/device-product.png')" />
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<h3
|
||||||
|
@click.stop="handleView(slotProps.id)"
|
||||||
|
style="font-weight: 600"
|
||||||
|
>
|
||||||
|
{{ slotProps.name }}
|
||||||
|
</h3>
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="12">
|
||||||
|
<div class="card-item-content-text">
|
||||||
|
设备类型
|
||||||
|
</div>
|
||||||
|
<div>直连设备</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"
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
>
|
||||||
|
<a-button :disabled="item.disabled">
|
||||||
|
<AIcon
|
||||||
|
type="DeleteOutlined"
|
||||||
|
v-if="item.key === 'delete'"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<AIcon :type="item.icon" />
|
||||||
|
<span>{{ item?.text }}</span>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<template v-else>
|
||||||
|
<a-button
|
||||||
|
:disabled="item.disabled"
|
||||||
|
@click="item.onClick"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="DeleteOutlined"
|
||||||
|
v-if="item.key === 'delete'"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<AIcon :type="item.icon" />
|
||||||
|
<span>{{ item?.text }}</span>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</CardBox>
|
||||||
|
</template>
|
||||||
|
<template #state="slotProps">
|
||||||
|
<a-badge
|
||||||
|
:text="slotProps.state === 1 ? '正常' : '禁用'"
|
||||||
|
:status="statusMap.get(slotProps.state)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #id="slotProps">
|
||||||
|
<a>{{ slotProps.id }}</a>
|
||||||
|
</template>
|
||||||
|
<template #action="slotProps">
|
||||||
|
<a-space :size="16">
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-bind="item.tooltip"
|
v-for="i in getActions(slotProps)"
|
||||||
:title="item.disabled && item.tooltip.title"
|
:key="i.key"
|
||||||
|
v-bind="i.tooltip"
|
||||||
>
|
>
|
||||||
<a-popconfirm
|
<a-popconfirm
|
||||||
v-if="item.popConfirm"
|
v-if="i.popConfirm"
|
||||||
v-bind="item.popConfirm"
|
v-bind="i.popConfirm"
|
||||||
:disabled="item.disabled"
|
|
||||||
okText="确定"
|
okText="确定"
|
||||||
cancelText="取消"
|
cancelText="取消"
|
||||||
>
|
>
|
||||||
<a-button :disabled="item.disabled">
|
|
||||||
<AIcon
|
|
||||||
type="DeleteOutlined"
|
|
||||||
v-if="item.key === 'delete'"
|
|
||||||
/>
|
|
||||||
<template v-else>
|
|
||||||
<AIcon :type="item.icon" />
|
|
||||||
<span>{{ item?.text }}</span>
|
|
||||||
</template>
|
|
||||||
</a-button>
|
|
||||||
</a-popconfirm>
|
|
||||||
<template v-else>
|
|
||||||
<a-button
|
<a-button
|
||||||
:disabled="item.disabled"
|
:disabled="i.disabled"
|
||||||
@click="item.onClick"
|
style="padding: 0"
|
||||||
>
|
type="link"
|
||||||
<AIcon
|
><AIcon :type="i.icon"
|
||||||
type="DeleteOutlined"
|
/></a-button>
|
||||||
v-if="item.key === 'delete'"
|
</a-popconfirm>
|
||||||
/>
|
<a-button
|
||||||
<template v-else>
|
style="padding: 0"
|
||||||
<AIcon :type="item.icon" />
|
type="link"
|
||||||
<span>{{ item?.text }}</span>
|
v-else
|
||||||
</template>
|
@click="i.onClick && i.onClick(slotProps)"
|
||||||
</a-button>
|
>
|
||||||
</template>
|
<a-button
|
||||||
|
:disabled="i.disabled"
|
||||||
|
style="padding: 0"
|
||||||
|
type="link"
|
||||||
|
><AIcon :type="i.icon"
|
||||||
|
/></a-button>
|
||||||
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</a-space>
|
||||||
</CardBox>
|
</template>
|
||||||
</template>
|
</JTable>
|
||||||
<template #state="slotProps">
|
<!-- 新增、编辑 -->
|
||||||
<a-badge
|
<Save
|
||||||
:text="slotProps.state === 1 ? '正常' : '禁用'"
|
ref="saveRef"
|
||||||
:status="statusMap.get(slotProps.state)"
|
:isAdd="isAdd"
|
||||||
/>
|
:title="title"
|
||||||
</template>
|
@success="refresh"
|
||||||
<template #id="slotProps">
|
/>
|
||||||
<a>{{ slotProps.id }}</a>
|
</a-card>
|
||||||
</template>
|
</page-container>
|
||||||
<template #action="slotProps">
|
|
||||||
<a-space :size="16">
|
|
||||||
<a-tooltip
|
|
||||||
v-for="i in getActions(slotProps)"
|
|
||||||
:key="i.key"
|
|
||||||
v-bind="i.tooltip"
|
|
||||||
>
|
|
||||||
<a-popconfirm
|
|
||||||
v-if="i.popConfirm"
|
|
||||||
v-bind="i.popConfirm"
|
|
||||||
okText="确定"
|
|
||||||
cancelText="取消"
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
<!-- 新增、编辑 -->
|
|
||||||
<Save ref="saveRef" :isAdd="isAdd" :title="title" @success="refresh" />
|
|
||||||
</a-card>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -195,7 +202,7 @@ import Save from './Save/index.vue';
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isAdd = ref<number>(0);
|
const isAdd = ref<number>(0);
|
||||||
const title = ref<string>('');
|
const title = ref<string>('');
|
||||||
const params = <Record<string, any>>{};
|
const params = ref<Record<string, any>>({});
|
||||||
const statusMap = new Map();
|
const statusMap = new Map();
|
||||||
statusMap.set(1, 'success');
|
statusMap.set(1, 'success');
|
||||||
statusMap.set(0, 'error');
|
statusMap.set(0, 'error');
|
||||||
|
|
|
@ -45,11 +45,9 @@
|
||||||
<div class="card-item-content">
|
<div class="card-item-content">
|
||||||
<h3
|
<h3
|
||||||
@click="handlEye(slotProps.id)"
|
@click="handlEye(slotProps.id)"
|
||||||
class="card-item-content-title"
|
class="card-item-content-title card-item-content-title-a"
|
||||||
>
|
>
|
||||||
<a class="card-item-content-title-a">{{
|
{{ slotProps.name }}
|
||||||
slotProps.name
|
|
||||||
}}</a>
|
|
||||||
</h3>
|
</h3>
|
||||||
<a-row class="card-item-content-box">
|
<a-row class="card-item-content-box">
|
||||||
<a-col
|
<a-col
|
||||||
|
@ -402,9 +400,9 @@ const handleSearch = (e: any) => {
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
|
|
||||||
.card-item-content-title-a {
|
.card-item-content-title-a {
|
||||||
// color: #000 !important;
|
color: #1890ff !important;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
overflow: hidden; //超出的文本隐藏
|
overflow: hidden; //超出的文本隐藏
|
||||||
text-overflow: ellipsis; //溢出用省略号显示
|
text-overflow: ellipsis; //溢出用省略号显示
|
||||||
white-space: nowrap; //溢出不换行
|
white-space: nowrap; //溢出不换行
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="card-item-content">
|
<div class="card-item-content">
|
||||||
<h3 class="card-item-content-title-a">
|
<h3 class="card-item-content-title card-item-content-title-a">
|
||||||
{{ slotProps.name }}
|
{{ slotProps.name }}
|
||||||
</h3>
|
</h3>
|
||||||
<a-row class="card-item-content-box">
|
<a-row class="card-item-content-box">
|
||||||
|
@ -299,9 +299,9 @@ const handleSearch = (e: any) => {
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
|
|
||||||
.card-item-content-title-a {
|
.card-item-content-title-a {
|
||||||
// color: #000 !important;
|
color: #000 !important;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
overflow: hidden; //超出的文本隐藏
|
overflow: hidden; //超出的文本隐藏
|
||||||
text-overflow: ellipsis; //溢出用省略号显示
|
text-overflow: ellipsis; //溢出用省略号显示
|
||||||
white-space: nowrap; //溢出不换行
|
white-space: nowrap; //溢出不换行
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,82 @@
|
||||||
|
export const FormStates = {
|
||||||
|
name: '',
|
||||||
|
type: 'UDP',
|
||||||
|
shareCluster: true,
|
||||||
|
parserType: '',
|
||||||
|
configuration: {
|
||||||
|
port: '',
|
||||||
|
host: '0.0.0.0',
|
||||||
|
publicPort: '',
|
||||||
|
publicHost: '',
|
||||||
|
remoteHost: '',
|
||||||
|
remotePort: '',
|
||||||
|
secure: false,
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
topicPrefix: '',
|
||||||
|
maxMessageSize: '',
|
||||||
|
certId: '',
|
||||||
|
privateKeyAlias: '',
|
||||||
|
clientId: '',
|
||||||
|
parserConfiguration: {
|
||||||
|
delimited: '',
|
||||||
|
lang: '',
|
||||||
|
script: '',
|
||||||
|
size: '',
|
||||||
|
length: '',
|
||||||
|
offset: '',
|
||||||
|
little: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const VisibleMost = [
|
||||||
|
'COAP_SERVER',
|
||||||
|
'MQTT_SERVER',
|
||||||
|
'WEB_SOCKET_SERVER',
|
||||||
|
'TCP_SERVER',
|
||||||
|
'UDP',
|
||||||
|
'HTTP_SERVER',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const VisibleData = {
|
||||||
|
parserType: ['TCP_SERVER'],
|
||||||
|
// configuration: {
|
||||||
|
port: VisibleMost,
|
||||||
|
host: VisibleMost,
|
||||||
|
publicPort: VisibleMost,
|
||||||
|
publicHost: VisibleMost,
|
||||||
|
remoteHost: ['MQTT_CLIENT'],
|
||||||
|
remotePort: ['MQTT_CLIENT'],
|
||||||
|
secure: ['TCP_SERVER', 'UDP', 'COAP_SERVER'],
|
||||||
|
username: ['MQTT_CLIENT'],
|
||||||
|
password: ['MQTT_CLIENT'],
|
||||||
|
topicPrefix: ['MQTT_CLIENT'],
|
||||||
|
maxMessageSize: ['MQTT_SERVER', 'MQTT_CLIENT'],
|
||||||
|
// certId: '',
|
||||||
|
// privateKeyAlias: '',
|
||||||
|
clientId: ['MQTT_CLIENT'],
|
||||||
|
// parserConfiguration: {
|
||||||
|
delimited: ['DELIMITED'],
|
||||||
|
lang: ['SCRIPT'],
|
||||||
|
script: ['SCRIPT'],
|
||||||
|
size: ['FIXED_LENGTH'],
|
||||||
|
length: ['LENGTH_FIELD'],
|
||||||
|
offset: ['LENGTH_FIELD'],
|
||||||
|
little: ['LENGTH_FIELD'],
|
||||||
|
// },
|
||||||
|
|
||||||
|
// },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ParserTypeOptions = [
|
||||||
|
{ value: 'DIRECT', label: '不处理' },
|
||||||
|
{ value: 'DELIMITED', label: '分隔符' },
|
||||||
|
{ value: 'SCRIPT', label: '自定义脚本' },
|
||||||
|
{ value: 'FIXED_LENGTH', label: '固定长度' },
|
||||||
|
{ value: 'LENGTH_FIELD', label: '长度字段' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const isVisible = (LastName: string, dependencies: string | boolean) =>
|
||||||
|
VisibleData[LastName].includes(dependencies);
|
|
@ -0,0 +1,461 @@
|
||||||
|
<template>
|
||||||
|
<page-container>
|
||||||
|
<div>
|
||||||
|
<Search :columns="columns" target="search" @search="handleSearch" />
|
||||||
|
|
||||||
|
<JTable
|
||||||
|
ref="tableRef"
|
||||||
|
:columns="columns"
|
||||||
|
:gridColumn="3"
|
||||||
|
:request="query"
|
||||||
|
:defaultParams="{
|
||||||
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
|
}"
|
||||||
|
:params="params"
|
||||||
|
>
|
||||||
|
<template #headerTitle>
|
||||||
|
<a-button type="primary" @click="handlAdd"
|
||||||
|
><plus-outlined />新增</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
<template #card="slotProps">
|
||||||
|
<CardBox
|
||||||
|
:showStatus="true"
|
||||||
|
:value="slotProps"
|
||||||
|
:actions="getActions(slotProps, 'card')"
|
||||||
|
v-bind="slotProps"
|
||||||
|
:class="
|
||||||
|
slotProps.state.value === 'disabled'
|
||||||
|
? 'tableCardDisabled'
|
||||||
|
: 'tableCardEnabled'
|
||||||
|
"
|
||||||
|
:status="slotProps.state.value"
|
||||||
|
:statusText="slotProps.state.text"
|
||||||
|
:statusNames="{
|
||||||
|
enabled: 'success',
|
||||||
|
disabled: 'error',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #img>
|
||||||
|
<slot name="img">
|
||||||
|
<img :src="getImage('/network.png')" />
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<div class="card-item-content">
|
||||||
|
<!-- <a
|
||||||
|
@click="handlEye(slotProps.id)"
|
||||||
|
class="card-item-content-title-a"
|
||||||
|
>
|
||||||
|
{{ slotProps.name }}
|
||||||
|
</a> -->
|
||||||
|
<h3
|
||||||
|
@click="handlEye(slotProps.id)"
|
||||||
|
class="card-item-content-title card-item-content-title-a"
|
||||||
|
>
|
||||||
|
{{ slotProps.name }}
|
||||||
|
</h3>
|
||||||
|
<a-row class="card-item-content-box">
|
||||||
|
<a-col :span="12">
|
||||||
|
<div class="card-item-content-text">
|
||||||
|
类型
|
||||||
|
</div>
|
||||||
|
<div class="card-item-content-text">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{
|
||||||
|
slotProps.type
|
||||||
|
}}</template>
|
||||||
|
{{ slotProps.type }}
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<div class="card-item-content-text">
|
||||||
|
详情
|
||||||
|
</div>
|
||||||
|
<div class="card-item-content-text">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{
|
||||||
|
getDetails(slotProps)
|
||||||
|
}}</template>
|
||||||
|
<span class="details-text">{{
|
||||||
|
getDetails(slotProps)
|
||||||
|
}}</span>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</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"
|
||||||
|
v-if="item.key === 'delete'"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<AIcon :type="item.icon" />
|
||||||
|
<span>{{ item.text }}</span>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<template v-else>
|
||||||
|
<a-button
|
||||||
|
:disabled="item.disabled"
|
||||||
|
@click="item.onClick"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="DeleteOutlined"
|
||||||
|
v-if="item.key === 'delete'"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<AIcon :type="item.icon" />
|
||||||
|
<span>{{ item.text }}</span>
|
||||||
|
</template>
|
||||||
|
</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>
|
||||||
|
<template #state="slotProps">
|
||||||
|
<a-badge
|
||||||
|
:text="slotProps.state.text"
|
||||||
|
:status="statusMap.get(slotProps.state.value)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #shareCluster="slotProps">
|
||||||
|
{{
|
||||||
|
slotProps.shareCluster === true
|
||||||
|
? '共享配置'
|
||||||
|
: '独立配置'
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
<template #type="slotProps">
|
||||||
|
{{ slotProps.typeObject.name }}
|
||||||
|
</template>
|
||||||
|
<template #details="slotProps">
|
||||||
|
{{ getDetails(slotProps) }}
|
||||||
|
</template>
|
||||||
|
</JTable>
|
||||||
|
</div>
|
||||||
|
</page-container>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup name="TypePage">
|
||||||
|
import type { ActionsType } from '@/components/Table/index.vue';
|
||||||
|
import { getImage } from '@/utils/comm';
|
||||||
|
import { supports, query, remove, start, shutdown } from '@/api/link/type';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const tableRef = ref<Record<string, any>>({});
|
||||||
|
const router = useRouter();
|
||||||
|
const params = ref<Record<string, any>>({});
|
||||||
|
const options = ref([]);
|
||||||
|
|
||||||
|
const statusMap = new Map();
|
||||||
|
statusMap.set('enabled', 'success');
|
||||||
|
statusMap.set('disabled', 'error');
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
ellipsis: true,
|
||||||
|
width: 250,
|
||||||
|
fixed: 'left',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '类型',
|
||||||
|
dataIndex: 'type',
|
||||||
|
key: 'type',
|
||||||
|
ellipsis: true,
|
||||||
|
width: 150,
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: options,
|
||||||
|
},
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '集群',
|
||||||
|
dataIndex: 'shareCluster',
|
||||||
|
key: 'shareCluster',
|
||||||
|
width: 120,
|
||||||
|
ellipsis: true,
|
||||||
|
scopedSlots: true,
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{ label: '共享配置', value: true },
|
||||||
|
{ label: '独立配置', value: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '详情',
|
||||||
|
dataIndex: 'details',
|
||||||
|
key: 'details',
|
||||||
|
ellipsis: true,
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'state',
|
||||||
|
key: 'state',
|
||||||
|
width: 100,
|
||||||
|
ellipsis: true,
|
||||||
|
scopedSlots: true,
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{ label: '正常', value: 'enabled' },
|
||||||
|
{ label: '禁用', value: 'disabled' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '说明',
|
||||||
|
dataIndex: 'description',
|
||||||
|
key: 'description',
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 200,
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getActions = (
|
||||||
|
data: Partial<Record<string, any>>,
|
||||||
|
type: 'card' | 'table',
|
||||||
|
): ActionsType[] => {
|
||||||
|
if (!data) return [];
|
||||||
|
const state = data.state.value;
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
key: 'eye',
|
||||||
|
text: '查看',
|
||||||
|
tooltip: {
|
||||||
|
title: '查看',
|
||||||
|
},
|
||||||
|
icon: 'EyeOutlined',
|
||||||
|
onClick: async () => {
|
||||||
|
handlEye(data.id);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'edit',
|
||||||
|
text: '编辑',
|
||||||
|
tooltip: {
|
||||||
|
title: '编辑',
|
||||||
|
},
|
||||||
|
icon: 'EditOutlined',
|
||||||
|
onClick: () => {
|
||||||
|
handlEdit(data.id);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'action',
|
||||||
|
text: state === 'enabled' ? '禁用' : '启用',
|
||||||
|
tooltip: {
|
||||||
|
title: state === 'enabled' ? '禁用' : '启用',
|
||||||
|
},
|
||||||
|
icon: state === 'enabled' ? 'StopOutlined' : 'CheckCircleOutlined',
|
||||||
|
popConfirm: {
|
||||||
|
title: `确认${state === 'enabled' ? '禁用' : '启用'}?`,
|
||||||
|
onConfirm: async () => {
|
||||||
|
let res =
|
||||||
|
state === 'enabled'
|
||||||
|
? await shutdown(data.id)
|
||||||
|
: await start(data.id);
|
||||||
|
if (res.success) {
|
||||||
|
message.success('操作成功');
|
||||||
|
tableRef.value?.reload();
|
||||||
|
} else {
|
||||||
|
message.error('操作失败!');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'delete',
|
||||||
|
text: '删除',
|
||||||
|
disabled: state === 'enabled',
|
||||||
|
tooltip: {
|
||||||
|
title:
|
||||||
|
state === 'enabled' ? '请先禁用该组件,再删除。' : '删除',
|
||||||
|
},
|
||||||
|
popConfirm: {
|
||||||
|
title: '确认删除?',
|
||||||
|
onConfirm: async () => {
|
||||||
|
const res = await remove(data.id);
|
||||||
|
if (res.success) {
|
||||||
|
message.success('操作成功');
|
||||||
|
tableRef.value.reload();
|
||||||
|
} else {
|
||||||
|
message.error('操作失败!');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: 'DeleteOutlined',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return type === 'table'
|
||||||
|
? actions
|
||||||
|
: actions.filter((item) => item.key !== 'eye');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlAdd = () => {
|
||||||
|
router.push({
|
||||||
|
path: `/iot/link/type/detail/:id`,
|
||||||
|
query: { view: false },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlEye = (id: string) => {
|
||||||
|
router.push({
|
||||||
|
path: `/iot/link/type/detail/${id}`,
|
||||||
|
query: { view: true },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlEdit = (id: string) => {
|
||||||
|
router.push({
|
||||||
|
path: `/iot/link/type/detail/${id}`,
|
||||||
|
query: { view: false },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlDelete = async (id: string) => {
|
||||||
|
const res = await remove(id);
|
||||||
|
if (res.success) {
|
||||||
|
message.success('操作成功');
|
||||||
|
tableRef.value.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDetails = (slotProps: Partial<Record<string, any>>) => {
|
||||||
|
const { typeObject, shareCluster, configuration, cluster } = slotProps;
|
||||||
|
const headers =
|
||||||
|
typeObject.name.replace(/[^a-zA-Z]/g, '').toLowerCase() + '://';
|
||||||
|
const content = !!shareCluster
|
||||||
|
? (configuration.publicHost || configuration.remoteHost) +
|
||||||
|
':' +
|
||||||
|
(configuration.publicPort || configuration.remotePort)
|
||||||
|
: (cluster[0].configuration.publicHost ||
|
||||||
|
cluster[0].configuration.remoteHost) +
|
||||||
|
':' +
|
||||||
|
(cluster[0].configuration.publicPort ||
|
||||||
|
cluster[0].configuration.remotePort);
|
||||||
|
|
||||||
|
return headers + content;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSupports = async () => {
|
||||||
|
const res = await supports();
|
||||||
|
options.value = res.result.map((item) => ({
|
||||||
|
value: item.id,
|
||||||
|
label: item.name,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
getSupports();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
const handleSearch = (e: any) => {
|
||||||
|
params.value = e;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tableCardDisabled {
|
||||||
|
width: 100%;
|
||||||
|
background: url('/images/access-config-diaabled.png') no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableCardEnabled {
|
||||||
|
width: 100%;
|
||||||
|
background: url('/images/access-config-enabled.png') no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-item-content {
|
||||||
|
min-height: 100px;
|
||||||
|
|
||||||
|
.card-item-content-title-a {
|
||||||
|
// color: #000 !important;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
overflow: hidden; //超出的文本隐藏
|
||||||
|
text-overflow: ellipsis; //溢出用省略号显示
|
||||||
|
white-space: nowrap; //溢出不换行
|
||||||
|
}
|
||||||
|
.card-item-content-box {
|
||||||
|
min-height: 50px;
|
||||||
|
}
|
||||||
|
.card-item-content-text {
|
||||||
|
color: rgba(0, 0, 0, 0.75);
|
||||||
|
font-size: 12px;
|
||||||
|
overflow: hidden; //超出的文本隐藏
|
||||||
|
text-overflow: ellipsis; //溢出用省略号显示
|
||||||
|
white-space: nowrap; //溢出不换行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.details-text {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -265,7 +265,7 @@
|
||||||
</page-container>
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup name="CertificateDetail">
|
<script lang="ts" setup name="StreamDetail">
|
||||||
import { message, Form } from 'ant-design-vue';
|
import { message, Form } from 'ant-design-vue';
|
||||||
import { queryProviders, queryDetail, save, update } from '@/api/media/stream';
|
import { queryProviders, queryDetail, save, update } from '@/api/media/stream';
|
||||||
import type { FormInstance } from 'ant-design-vue';
|
import type { FormInstance } from 'ant-design-vue';
|
||||||
|
|
|
@ -45,12 +45,12 @@
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="card-item-content">
|
<div class="card-item-content">
|
||||||
<a
|
<h3
|
||||||
@click="handlEye(slotProps.id)"
|
@click="handlEye(slotProps.id)"
|
||||||
class="card-item-content-title-a"
|
class="card-item-content-title card-item-content-title-a"
|
||||||
>
|
>
|
||||||
{{ slotProps.name }}
|
{{ slotProps.name }}
|
||||||
</a>
|
</h3>
|
||||||
<a-row class="card-item-content-box">
|
<a-row class="card-item-content-box">
|
||||||
<a-col
|
<a-col
|
||||||
:span="8"
|
:span="8"
|
||||||
|
@ -149,7 +149,7 @@
|
||||||
</div>
|
</div>
|
||||||
</page-container>
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup name="AccessConfigPage">
|
<script lang="ts" setup name="StreamPage">
|
||||||
import type { ActionsType } from '@/components/Table/index.vue';
|
import type { ActionsType } from '@/components/Table/index.vue';
|
||||||
import { getImage } from '@/utils/comm';
|
import { getImage } from '@/utils/comm';
|
||||||
import { query, remove, disable, enalbe } from '@/api/media/stream';
|
import { query, remove, disable, enalbe } from '@/api/media/stream';
|
||||||
|
@ -300,7 +300,7 @@ const handleSearch = (e: any) => {
|
||||||
.card-item-content-title-a {
|
.card-item-content-title-a {
|
||||||
// color: #000 !important;
|
// color: #000 !important;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
overflow: hidden; //超出的文本隐藏
|
overflow: hidden; //超出的文本隐藏
|
||||||
text-overflow: ellipsis; //溢出用省略号显示
|
text-overflow: ellipsis; //溢出用省略号显示
|
||||||
white-space: nowrap; //溢出不换行
|
white-space: nowrap; //溢出不换行
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
<template>
|
||||||
|
<div class="doc">
|
||||||
|
<div className="url">
|
||||||
|
阿里云物联网平台:
|
||||||
|
<a
|
||||||
|
:style="{ wordBreak: 'break-all' }"
|
||||||
|
href="https://help.aliyun.com/document_detail/87368.html"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
https://help.aliyun.com/document_detail/87368.html
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<h1>1. 概述</h1>
|
||||||
|
<div>
|
||||||
|
在特定场景下,设备无法直接接入阿里云物联网平台时,您可先将设备接入物联网平台,再使用阿里云“云云对接SDK”,快速构建桥接服务,搭建物联网平台与阿里云物联网平台的双向数据通道。
|
||||||
|
</div>
|
||||||
|
<div class="image">
|
||||||
|
<a-image width="100%" :src="getImage('/northbound/aliyun2.png')" />
|
||||||
|
</div>
|
||||||
|
<h1>2.配置说明</h1>
|
||||||
|
<div>
|
||||||
|
<h2> 1、服务地址</h2>
|
||||||
|
<div>
|
||||||
|
阿里云内部给每台机器设置的唯一编号。请根据购买的阿里云服务器地址进行选择。
|
||||||
|
</div>
|
||||||
|
<div>获取路径:“阿里云物联网平台”--“服务地址”</div>
|
||||||
|
<div class="image">
|
||||||
|
<a-image width="100%" :src="getImage('/northbound/aliyun3.png')" />
|
||||||
|
</div>
|
||||||
|
<h2> 2、AccesskeyID/Secret</h2>
|
||||||
|
<div>
|
||||||
|
用于程序通知方式调用云服务费API的用户标识和秘钥获取路径:“阿里云管理控制台”--“用户头像”--“”--“AccessKey管理”--“查看”
|
||||||
|
</div>
|
||||||
|
<div class="image">
|
||||||
|
<a-image width="100%" :src="getImage('/northbound/aliyun1.jpg')" />
|
||||||
|
</div>
|
||||||
|
<h2> 3. 网桥产品</h2>
|
||||||
|
<div>
|
||||||
|
物联网平台对于阿里云物联网平台,是一个网关设备,需要映射到阿里云物联网平台的具体产品
|
||||||
|
</div>
|
||||||
|
<h2> 4. 产品映射</h2>
|
||||||
|
<div>
|
||||||
|
将阿里云物联网平台中的产品实例与物联网平台的产品实例进行关联。关联后需要进入该产品下的每一个设备的实例信息页,填入对应的阿里云物联网平台设备的DeviceName、DeviceSecret进行一对一绑定。
|
||||||
|
</div>
|
||||||
|
<div class="image">
|
||||||
|
<a-image width="100%" :src="getImage('/northbound/aliyun4.png')" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { getImage } from '@/utils/comm';
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.doc {
|
||||||
|
height: 1000px;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,333 @@
|
||||||
|
<template>
|
||||||
|
<page-container>
|
||||||
|
<a-card>
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="16">
|
||||||
|
<TitleComponent data="基本信息" />
|
||||||
|
<a-form
|
||||||
|
:layout="'vertical'"
|
||||||
|
ref="formRef"
|
||||||
|
:model="modelRef"
|
||||||
|
>
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item label="名称" name="name" :rules=" [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
max: 64,
|
||||||
|
message: '最多输入64个字符',
|
||||||
|
},
|
||||||
|
]">
|
||||||
|
<a-input placeholder="请输入名称" v-model:value="modelRef.name" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item :name="['accessConfig', 'regionId']" :rules="[{
|
||||||
|
required: true,
|
||||||
|
message: '请选择服务地址',
|
||||||
|
}]">
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
服务地址
|
||||||
|
<a-tooltip title="阿里云内部给每台机器设置的唯一编号">
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px;" />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<a-select placeholder="请选择服务地址" v-model:value="modelRef.accessConfig.regionId" show-search :filter-option="filterOption" @blur="productChange">
|
||||||
|
<a-select-option v-for="item in regionsList" :key="item.id" :value="item.id" :label="item.name">{{item.name}}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item :name="['accessConfig', 'instanceId']">
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
实例ID
|
||||||
|
<a-tooltip title="阿里云物联网平台中的实例ID,没有则不填">
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px;" />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<a-input placeholder="请输入实例ID" v-model:value="modelRef.accessConfig.instanceId" @blur="productChange" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item :name="['accessConfig', 'accessKeyId']" :rules="[{
|
||||||
|
required: true,
|
||||||
|
message: '请输入accessKey',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
max: 64,
|
||||||
|
message: '最多输入64个字符',
|
||||||
|
},
|
||||||
|
]">
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
accessKey
|
||||||
|
<a-tooltip title="用于程序通知方式调用云服务API的用户标识">
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px;" />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<a-input placeholder="请输入accessKey" v-model:value="modelRef.accessConfig.accessKeyId" @blur="productChange" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item :name="['accessConfig', 'accessSecret']" :rules="[{
|
||||||
|
required: true,
|
||||||
|
message: '请输入accessSecret',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
max: 64,
|
||||||
|
message: '最多输入64个字符',
|
||||||
|
},
|
||||||
|
]">
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
accessSecret
|
||||||
|
<a-tooltip title="用于程序通知方式调用云服务费API的秘钥标识">
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px;" />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<a-input placeholder="请输入accessSecret" v-model:value="modelRef.accessConfig.accessSecret" @blur="productChange" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item name="bridgeProductKey" :rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请选择网桥产品',
|
||||||
|
}">
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
网桥产品
|
||||||
|
<a-tooltip title="物联网平台对应的阿里云产品">
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px;" />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<a-select placeholder="请选择网桥产品" v-model:value="modelRef.bridgeProductKey" show-search :filter-option="filterOption">
|
||||||
|
<a-select-option v-for="item in aliyunProductList" :key="item.productKey" :value="item.productKey" :label="item.productName">{{item.productName}}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<p>产品映射</p>
|
||||||
|
<a-collapse v-if="modelRef.mappings.length" :activeKey="modelRef.mappings.map((_, _index) => _index)">
|
||||||
|
<a-collapse-panel v-for="(item, index) in modelRef.mappings" :key="index" :header="item.productKey ? aliyunProductList.find(i => i.productKey === item.productKey)?.productName : `产品映射${index + 1}`">
|
||||||
|
<template #extra><AIcon type="DeleteOutlined" @click="delItem(index)" /></template>
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="阿里云产品" :name="['mappings', index, 'productKey']" :rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请选择阿里云产品',
|
||||||
|
}">
|
||||||
|
<a-select placeholder="请选择阿里云产品" v-model:value="item.productKey" show-search :filter-option="filterOption">
|
||||||
|
<a-select-option v-for="i in getAliyunProductList(item.productKey)" :key="i.productKey" :value="i.productKey" :label="i.productName">{{i.productName}}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="平台产品" :name="['mappings', index, 'productId']" :rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请选择平台产品',
|
||||||
|
}">
|
||||||
|
<a-select placeholder="请选择平台产品" v-model:value="item.productId" show-search :filter-option="filterOption">
|
||||||
|
<a-select-option v-for="i in getPlatProduct(item.productId)" :key="i.id" :value="item.id" :label="i.name">{{i.name}}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-button type="dashed" style="width: 100%; margin-top: 10px" @click="addItem">
|
||||||
|
<AIcon
|
||||||
|
type="PlusOutlined"
|
||||||
|
style="margin-left: 2px;" />添加
|
||||||
|
</a-button>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24" style="margin-top: 20px">
|
||||||
|
<a-form-item label="说明" name="description" :rules="{
|
||||||
|
max: 200,
|
||||||
|
message: '最多输入200个字符',
|
||||||
|
}">
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="modelRef.description"
|
||||||
|
placeholder="请输入说明"
|
||||||
|
showCount
|
||||||
|
:maxlength="200"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
<div v-if="type === 'edit'">
|
||||||
|
<a-button :loading="loading" type="primary" @click="saveBtn">保存</a-button>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<Doc />
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
</page-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Doc from './doc.vue'
|
||||||
|
import {savePatch, detail, getRegionsList, getAliyunProductsList, queryProductList } from '@/api/northbound/alicloud'
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
|
||||||
|
const modelRef = reactive({
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
accessConfig: {
|
||||||
|
regionId: undefined,
|
||||||
|
instanceId: undefined,
|
||||||
|
accessKeyId: undefined,
|
||||||
|
accessSecret: undefined
|
||||||
|
},
|
||||||
|
bridgeProductKey: undefined,
|
||||||
|
bridgeProductName: undefined,
|
||||||
|
mappings: [{
|
||||||
|
productKey: undefined,
|
||||||
|
productId: undefined,
|
||||||
|
}],
|
||||||
|
description: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
const addItem = () => {
|
||||||
|
modelRef.mappings.push({
|
||||||
|
productKey: undefined,
|
||||||
|
productId: undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const delItem = (index: number) => {
|
||||||
|
modelRef.mappings.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const productList = ref<Record<string, any>[]>([])
|
||||||
|
const regionsList = ref<Record<string, any>[]>([])
|
||||||
|
const aliyunProductList = ref<Record<string, any>[]>([])
|
||||||
|
const loading = ref<boolean>(false)
|
||||||
|
const type = ref<'edit' | 'view'>('view')
|
||||||
|
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryRegionsList = async () => {
|
||||||
|
const resp = await getRegionsList()
|
||||||
|
if(resp.status === 200){
|
||||||
|
regionsList.value = resp.result as Record<string, any>[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getProduct = async () => {
|
||||||
|
const resp = await queryProductList({
|
||||||
|
paging: false,
|
||||||
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
|
})
|
||||||
|
if(resp.status === 200){
|
||||||
|
productList.value = (resp?.result as Record<string, any>[])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAliyunProduct = async (data: any) => {
|
||||||
|
if(data.regionId && data.accessKeyId && data.accessSecret){
|
||||||
|
const resp: any = await getAliyunProductsList(data)
|
||||||
|
if(resp.status === 200){
|
||||||
|
aliyunProductList.value = (resp?.result?.data as Record<string, any>[])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const productChange = () => {
|
||||||
|
const data = modelRef.accessConfig
|
||||||
|
getAliyunProduct(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPlatProduct = (val: string) => {
|
||||||
|
const arr = modelRef.mappings.map(item => item?.productId) || []
|
||||||
|
const checked = _.cloneDeep(arr)
|
||||||
|
const _index = checked.findIndex(i => i === val)
|
||||||
|
checked.splice(_index, 1)
|
||||||
|
const list = productList.value.filter((i: any) => !checked.includes(i?.id as any))
|
||||||
|
return list || []
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAliyunProductList = (val: string) => {
|
||||||
|
const items = modelRef.mappings.map((item) => item?.productKey) || []
|
||||||
|
const checked = _.cloneDeep(items)
|
||||||
|
const _index = checked.findIndex(i => i === val)
|
||||||
|
checked.splice(_index, 1)
|
||||||
|
const list = aliyunProductList.value?.filter((i: any) => !checked.includes(i?.productKey as any))
|
||||||
|
return list || []
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveBtn = async () => {
|
||||||
|
const data = await formRef.value.validate()
|
||||||
|
const product = (aliyunProductList.value || []).find(
|
||||||
|
(item: any) => item?.bridgeProductKey === data?.bridgeProductKey,
|
||||||
|
);
|
||||||
|
data.bridgeProductName = product?.productName || '';
|
||||||
|
loading.value = true;
|
||||||
|
const resp = await savePatch(toRaw(modelRef));
|
||||||
|
loading.value = false;
|
||||||
|
if (resp.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
formRef.value.resetFields();
|
||||||
|
router.push('/iot/northbound/AliCloud/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
() => route.params?.id,
|
||||||
|
async (newId) => {
|
||||||
|
if(newId){
|
||||||
|
queryRegionsList()
|
||||||
|
getProduct()
|
||||||
|
if (newId === ':id' || !newId) return;
|
||||||
|
const resp = await detail(newId as string)
|
||||||
|
const _data: any = resp.result;
|
||||||
|
if (_data) {
|
||||||
|
getAliyunProduct(_data?.accessConfig)
|
||||||
|
}
|
||||||
|
Object.assign(modelRef, _data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{immediate: true, deep: true}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.query.type,
|
||||||
|
(newVal) => {
|
||||||
|
if(newVal){
|
||||||
|
type.value = newVal as 'edit' | 'view'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{immediate: true, deep: true}
|
||||||
|
);
|
||||||
|
</script>
|
|
@ -1,7 +1,306 @@
|
||||||
<template>
|
<template>
|
||||||
<page-container>阿里云</page-container>
|
<page-container>
|
||||||
|
<Search :columns="columns" target="northbound-dueros" :params="params" />
|
||||||
|
<JTable
|
||||||
|
ref="instanceRef"
|
||||||
|
:columns="columns"
|
||||||
|
:request="query"
|
||||||
|
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
|
||||||
|
:params="params"
|
||||||
|
>
|
||||||
|
<template #headerTitle>
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="handleAdd">新增</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<template #card="slotProps">
|
||||||
|
<CardBox
|
||||||
|
:value="slotProps"
|
||||||
|
:actions="getActions(slotProps, 'card')"
|
||||||
|
v-bind="slotProps"
|
||||||
|
:status="slotProps.state?.value"
|
||||||
|
:statusText="slotProps.state?.text"
|
||||||
|
:statusNames="{
|
||||||
|
enabled: 'success',
|
||||||
|
disabled: 'error'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #img>
|
||||||
|
<slot name="img">
|
||||||
|
<img
|
||||||
|
:src="
|
||||||
|
getImage('/northbound/aliyun.png')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<h3
|
||||||
|
class="card-item-content-title"
|
||||||
|
@click.stop="handleView(slotProps.id)"
|
||||||
|
>
|
||||||
|
{{ slotProps.name }}
|
||||||
|
</h3>
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="12">
|
||||||
|
<div class="card-item-content-text">
|
||||||
|
网桥产品
|
||||||
|
</div>
|
||||||
|
<div>{{ slotProps?.bridgeProductName }}</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<div class="card-item-content-text">
|
||||||
|
<label>说明</label>
|
||||||
|
</div>
|
||||||
|
<div>{{ slotProps?.description }}</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"
|
||||||
|
v-if="item.key === 'delete'"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<AIcon :type="item.icon" />
|
||||||
|
<span>{{ item?.text }}</span>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<template v-else>
|
||||||
|
<a-button
|
||||||
|
:disabled="item.disabled"
|
||||||
|
@click="item.onClick"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="DeleteOutlined"
|
||||||
|
v-if="item.key === 'delete'"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<AIcon :type="item.icon" />
|
||||||
|
<span>{{ item?.text }}</span>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</CardBox>
|
||||||
|
</template>
|
||||||
|
<template #state="slotProps">
|
||||||
|
<a-badge
|
||||||
|
:text="slotProps.state?.text"
|
||||||
|
:status="statusMap.get(slotProps.state?.value)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #action="slotProps">
|
||||||
|
<a-space :size="16">
|
||||||
|
<a-tooltip
|
||||||
|
v-for="i in getActions(slotProps, '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>
|
||||||
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
query,
|
||||||
|
_undeploy,
|
||||||
|
_deploy,
|
||||||
|
_delete
|
||||||
|
} from '@/api/northbound/alicloud';
|
||||||
|
import type { ActionsType } from '@/components/Table/index.vue';
|
||||||
|
import { getImage } from '@/utils/comm';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
</script>
|
const router = useRouter();
|
||||||
|
const instanceRef = ref<Record<string, any>>({});
|
||||||
|
const params = ref<Record<string, any>>({});
|
||||||
|
const current = ref<Record<string, any>>({});
|
||||||
|
|
||||||
|
const statusMap = new Map();
|
||||||
|
statusMap.set('enabled', 'success');
|
||||||
|
statusMap.set('disabled', 'error');
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '网桥产品',
|
||||||
|
dataIndex: 'bridgeProductName',
|
||||||
|
key: 'bridgeProductName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '说明',
|
||||||
|
dataIndex: 'describe',
|
||||||
|
key: 'describe',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'state',
|
||||||
|
key: 'state',
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 250,
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
*/
|
||||||
|
const handleAdd = () => {
|
||||||
|
router.push('/iot/northbound/AliCloud/detail/:id');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查看
|
||||||
|
*/
|
||||||
|
const handleView = (id: string) => {
|
||||||
|
router.push({
|
||||||
|
path: '/iot/northbound/AliCloud/detail/' + id,
|
||||||
|
query: {
|
||||||
|
type: 'view'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActions = (
|
||||||
|
data: Partial<Record<string, any>>,
|
||||||
|
type: 'card' | 'table',
|
||||||
|
): ActionsType[] => {
|
||||||
|
if (!data) return [];
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
key: 'view',
|
||||||
|
text: '查看',
|
||||||
|
tooltip: {
|
||||||
|
title: '查看',
|
||||||
|
},
|
||||||
|
icon: 'EyeOutlined',
|
||||||
|
onClick: () => {
|
||||||
|
handleView(data.id);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'edit',
|
||||||
|
text: '编辑',
|
||||||
|
tooltip: {
|
||||||
|
title: '编辑',
|
||||||
|
},
|
||||||
|
icon: 'EditOutlined',
|
||||||
|
onClick: () => {
|
||||||
|
router.push({
|
||||||
|
path: '/iot/northbound/AliCloud/detail/' + data.id,
|
||||||
|
query: {
|
||||||
|
type: 'edit'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'action',
|
||||||
|
text: data.state?.value !== 'disabled' ? '禁用' : '启用',
|
||||||
|
tooltip: {
|
||||||
|
title: data.state?.value !== 'disabled' ? '禁用' : '启用',
|
||||||
|
},
|
||||||
|
icon:
|
||||||
|
data.state.value !== 'notActive'
|
||||||
|
? 'StopOutlined'
|
||||||
|
: 'CheckCircleOutlined',
|
||||||
|
popConfirm: {
|
||||||
|
title: `确认${
|
||||||
|
data.state.value !== 'disabled' ? '禁用' : '启用'
|
||||||
|
}?`,
|
||||||
|
onConfirm: async () => {
|
||||||
|
let response = undefined;
|
||||||
|
if (data.state.value !== 'disabled') {
|
||||||
|
response = await _undeploy(data.id);
|
||||||
|
} else {
|
||||||
|
response = await _deploy(data.id);
|
||||||
|
}
|
||||||
|
if (response && response.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
instanceRef.value?.reload();
|
||||||
|
} else {
|
||||||
|
message.error('操作失败!');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'delete',
|
||||||
|
text: '删除',
|
||||||
|
disabled: data.state?.value !== 'disabled',
|
||||||
|
tooltip: {
|
||||||
|
title:
|
||||||
|
data.state.value !== 'disabled'
|
||||||
|
? '请先禁用该数据,再删除。'
|
||||||
|
: '删除',
|
||||||
|
},
|
||||||
|
popConfirm: {
|
||||||
|
title: '确认删除?',
|
||||||
|
onConfirm: async () => {
|
||||||
|
const resp = await _delete(data.id);
|
||||||
|
if (resp.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
instanceRef.value?.reload();
|
||||||
|
} else {
|
||||||
|
message.error('操作失败!');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: 'DeleteOutlined',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
if (type === 'card')
|
||||||
|
return actions.filter((i: ActionsType) => i.key !== 'view');
|
||||||
|
return actions;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
<template>
|
||||||
|
<a-table
|
||||||
|
rowKey="id"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="dataSource"
|
||||||
|
bordered
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, text, record }">
|
||||||
|
<div style="width: 280px">
|
||||||
|
<template v-if="['valueType', 'name'].includes(column.dataIndex)">
|
||||||
|
<span>{{ text }}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<ValueItem
|
||||||
|
v-model:modelValue="record.value"
|
||||||
|
:itemType="record.type"
|
||||||
|
:options="
|
||||||
|
record.type === 'enum'
|
||||||
|
? (record?.dataType?.elements || []).map(
|
||||||
|
(item) => {
|
||||||
|
return {
|
||||||
|
label: item.text,
|
||||||
|
value: item.value,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: record.type === 'boolean'
|
||||||
|
? [
|
||||||
|
{ label: '是', value: true },
|
||||||
|
{ label: '否', value: false },
|
||||||
|
]
|
||||||
|
: undefined
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { PropType } from "vue-demi";
|
||||||
|
|
||||||
|
|
||||||
|
type Emits = {
|
||||||
|
(e: 'update:modelValue', data: Record<string, any>[]): void;
|
||||||
|
};
|
||||||
|
const _emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
const _props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Array as PropType<Record<string, any>[]>,
|
||||||
|
default: '',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '参数名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
with: '33%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '类型',
|
||||||
|
dataIndex: 'valueType',
|
||||||
|
with: '33%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '值',
|
||||||
|
dataIndex: 'value',
|
||||||
|
with: '34%',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// const dataSource = ref<Record<any, any>[]>(_props.modelValue || []);
|
||||||
|
|
||||||
|
const dataSource = computed({
|
||||||
|
get: () => {
|
||||||
|
return _props.modelValue || {
|
||||||
|
messageType: undefined,
|
||||||
|
message: {
|
||||||
|
properties: undefined,
|
||||||
|
functionId: undefined,
|
||||||
|
inputs: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (val: any) => {
|
||||||
|
_emit('update:modelValue', val);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,139 @@
|
||||||
|
<template>
|
||||||
|
<a-form
|
||||||
|
:layout="'vertical'"
|
||||||
|
ref="formRef"
|
||||||
|
:model="modelRef"
|
||||||
|
>
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="24" v-if="actionType === 'command'">
|
||||||
|
<a-form-item name="messageType" label="指令类型" :rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请选择指令类型',
|
||||||
|
}">
|
||||||
|
<a-select placeholder="请选择指令类型" v-model:value="modelRef.messageType" show-search :filter-option="filterOption">
|
||||||
|
<a-select-option value="READ_PROPERTY">读取属性</a-select-option>
|
||||||
|
<a-select-option value="WRITE_PROPERTY">修改属性</a-select-option>
|
||||||
|
<a-select-option value="INVOKE_FUNCTION">调用功能</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="(modelRef.messageType === 'READ_PROPERTY' || actionType === 'latestData') ? 24 : 12" v-if="(actionType === 'command' && ['READ_PROPERTY','WRITE_PROPERTY'].includes(modelRef.messageType)) || actionType === 'latestData'">
|
||||||
|
<a-form-item :name="['message', 'properties']" label="属性" :rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请选择属性',
|
||||||
|
}">
|
||||||
|
<a-select placeholder="请选择属性" v-model:value="modelRef.message.properties" show-search :filter-option="filterOption">
|
||||||
|
<a-select-option v-for="i in (metadata?.properties) || []" :key="i.id" :value="i.id" :label="i.name">{{i.name}}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12" v-if="modelRef.messageType === 'WRITE_PROPERTY' && actionType === 'command'">
|
||||||
|
<a-form-item :name="['message', 'value']" label="值" :rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请输入值',
|
||||||
|
}">
|
||||||
|
<a-input />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24" v-if="modelRef.messageType === 'INVOKE_FUNCTION'">
|
||||||
|
<a-form-item :name="['message', 'functionId']" label="功能" :rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请选择功能',
|
||||||
|
}">
|
||||||
|
<a-select placeholder="请选择功能" v-model:value="modelRef.message.functionId" show-search :filter-option="filterOption" @change="funcChange">
|
||||||
|
<a-select-option v-for="i in (metadata?.functions) || []" :key="i.id" :value="i.id" :label="i.name">{{i.name}}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24" v-if="modelRef.messageType === 'INVOKE_FUNCTION' && modelRef.message.functionId">
|
||||||
|
<a-form-item :name="['message', 'inputs']" label="参数列表" :rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请输入参数列表',
|
||||||
|
}">
|
||||||
|
<EditTable v-model="modelRef.message.inputs"/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import EditTable from './EditTable.vue'
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
|
||||||
|
const funcList = ref<Record<string, any>[]>([])
|
||||||
|
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
actionType: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {
|
||||||
|
properties: [],
|
||||||
|
functions: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
type Emits = {
|
||||||
|
(e: 'update:modelValue', data: any): void;
|
||||||
|
};
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
const modelRef = computed({
|
||||||
|
get: () => {
|
||||||
|
return props.modelValue || {
|
||||||
|
messageType: undefined,
|
||||||
|
message: {
|
||||||
|
properties: undefined,
|
||||||
|
functionId: undefined,
|
||||||
|
inputs: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (val: any) => {
|
||||||
|
emit('update:modelValue', val);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const funcChange = (val: string) => {
|
||||||
|
if(val){
|
||||||
|
const arr = props.metadata?.functions.find((item: any) => item.id === val)?.inputs || []
|
||||||
|
const list = arr.map((item: any) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
value: undefined,
|
||||||
|
valueType: item?.valueType?.type,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
modelRef.value.message.inputs = list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveBtn = () => new Promise((resolve) => {
|
||||||
|
formRef.value.validate()
|
||||||
|
.then(() => {
|
||||||
|
resolve(toRaw(modelRef))
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
resolve(false)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({ saveBtn })
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,101 @@
|
||||||
|
<template>
|
||||||
|
<div class="doc">
|
||||||
|
<div class="url">
|
||||||
|
小度智能家居开放平台:
|
||||||
|
<a
|
||||||
|
href="https://dueros.baidu.com/dbp/bot/index#/iotopenplatform"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
https://dueros.baidu.com/dbp/bot/index#/iotopenplatform
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<h1>1. 概述</h1>
|
||||||
|
<div>
|
||||||
|
DuerOS支持家居场景下的云端控制,该页面主要将平台的产品与DuerOS支持语音控制的产品进行映射,以到达小度平台控制本平台设备的目的。
|
||||||
|
</div>
|
||||||
|
<h1>2. 操作步骤</h1>
|
||||||
|
<div>
|
||||||
|
<h2>1、在百度小度技能平台创建技能,并授权。完成物联网平台与DuerOS的关联。</h2>
|
||||||
|
<div class="image">
|
||||||
|
<a-image width="100%" :src="getImage('/cloud/dueros-doc.jpg')" />
|
||||||
|
</div>
|
||||||
|
<h1>授权地址</h1>
|
||||||
|
<div>物联网平台的登录地址。注意需要为https。</div>
|
||||||
|
<div>请复制并填写: https://{location.host}/#/user/login</div>
|
||||||
|
<h1>Client_Id</h1>
|
||||||
|
<div>请填写系统管理-应用管理中的clientId。</div>
|
||||||
|
<div class="image">
|
||||||
|
<a-image width="100%" :src="getImage('/cloud/dueros-doc1.png')" />
|
||||||
|
</div>
|
||||||
|
<h1>回调地址</h1>
|
||||||
|
<div>请复制DuerOS平台中的值,填写到系统管理-应用管理中-redirectUrl中。</div>
|
||||||
|
<div class="image">
|
||||||
|
<a-image width="100%" :src="getImage('/cloud/dueros-doc2.png')" />
|
||||||
|
</div>
|
||||||
|
<h1>Token地址</h1>
|
||||||
|
<div>请复制并填写:HTTPS://{location.host}/api/v1/token</div>
|
||||||
|
<h1>ClientSecret</h1>
|
||||||
|
<div>请复制系统管理-应用管理中的secureKey,填写到DuerOS平台。</div>
|
||||||
|
<div class="image">
|
||||||
|
<a-image width="100%" :src="getImage('/cloud/dueros-doc3.png')" />
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
<h1>WebService</h1>
|
||||||
|
<div>请复制并填写:/dueros/product/_query</div>
|
||||||
|
<h2>2、登录物联网平台,进行平台内产品与DuerOS产品的数据映射。</h2>
|
||||||
|
<h2>
|
||||||
|
3、智能家居用户通过物联网平台中的用户,登录小度APP,获取平台内当前用户的所属设备。获取后即可进行语音控制。
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<h1>3. 配置说明</h1>
|
||||||
|
<div>
|
||||||
|
<h2>
|
||||||
|
1、“设备类型”为DuerOS平台拟定的标准规范,设备类型将决定【动作映射】中“动作”的下拉选项,以及【属性映射】中“Dueros属性”的下拉选项
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { getImage } from '@/utils/comm';
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.doc {
|
||||||
|
height: 1000px;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,385 @@
|
||||||
|
<template>
|
||||||
|
<page-container>
|
||||||
|
<a-card>
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="16">
|
||||||
|
<TitleComponent data="基本信息" />
|
||||||
|
<a-form
|
||||||
|
:layout="'vertical'"
|
||||||
|
ref="formRef"
|
||||||
|
:model="modelRef"
|
||||||
|
>
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item label="名称" name="name" :rules=" [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
max: 64,
|
||||||
|
message: '最多输入64个字符',
|
||||||
|
},
|
||||||
|
]">
|
||||||
|
<a-input placeholder="请输入名称" v-model:value="modelRef.name" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="产品" name="id" :rules="[{
|
||||||
|
required: true,
|
||||||
|
message: '请选择产品',
|
||||||
|
}]">
|
||||||
|
<a-select :disabled="modelRef.id !== ':id'" placeholder="请选择产品" v-model:value="modelRef.id" show-search :filter-option="filterOption" @change="productChange">
|
||||||
|
<a-select-option v-for="item in productList" :key="item.id" :value="item.id" :label="item.name">{{item.name}}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item name="applianceType" :rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请选择设备类型',
|
||||||
|
}">
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
设备类型
|
||||||
|
<a-tooltip title="DuerOS平台拟定的规范">
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px;" />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<a-select placeholder="请选择设备类型" v-model:value="modelRef.applianceType" show-search :filter-option="filterOption" @change="typeChange">
|
||||||
|
<a-select-option v-for="item in typeList" :key="item.id" :value="item.id" :label="item.name">{{item.name}}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item name="productName" v-show="false" label="产品名称">
|
||||||
|
<a-input v-model:value="modelRef.productName" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<p>动作映射</p>
|
||||||
|
<a-collapse v-if="modelRef.actionMappings.length" :activeKey="modelRef.actionMappings.map((_, _index) => _index)">
|
||||||
|
<a-collapse-panel v-for="(item, index) in modelRef.actionMappings" :key="index" :header="item.action ? getTypesActions(item.action).find(i => i.id === item.action)?.name : `动作映射${index + 1}`">
|
||||||
|
<template #extra><AIcon type="DeleteOutlined" @click="delItem(index)" /></template>
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item :name="['actionMappings', index, 'action']" :rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请选择动作',
|
||||||
|
}">
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
动作
|
||||||
|
<a-tooltip title="DuerOS平台拟定的设备类型具有的相关动作">
|
||||||
|
<AIcon type="QuestionCircleOutlined" />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<a-select placeholder="请选择动作" v-model:value="item.action" show-search :filter-option="filterOption">
|
||||||
|
<a-select-option v-for="i in getTypesActions(item.action)" :key="i.id" :value="i.id" :label="i.name">{{i.name}}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item :name="['actionMappings', index, 'actionType']" :rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请选择操作',
|
||||||
|
}">
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
操作
|
||||||
|
<a-tooltip title="映射物联网平台中所选产品具备的动作">
|
||||||
|
<AIcon type="QuestionCircleOutlined" />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<a-select placeholder="请选择操作" v-model:value="item.actionType" show-search :filter-option="filterOption">
|
||||||
|
<a-select-option value="command">下发指令</a-select-option>
|
||||||
|
<a-select-option value="latestData">获取历史数据</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24" v-if="item.actionType">
|
||||||
|
<a-form-item :name="['actionMappings', index, 'command']">
|
||||||
|
<Command ref="command" :metadata="findProductMetadata" v-model:modelValue="item.command" :actionType="item.actionType" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-button type="dashed" style="width: 100%; margin-top: 10px" @click="addItem">
|
||||||
|
<AIcon
|
||||||
|
type="PlusOutlined"
|
||||||
|
style="margin-left: 2px;" />新增动作
|
||||||
|
</a-button>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<p style="margin-top: 20px">属性映射</p>
|
||||||
|
<a-collapse v-if="modelRef.propertyMappings.length" :activeKey="modelRef.propertyMappings.map((_, _index) => _index)">
|
||||||
|
<a-collapse-panel v-for="(item, index) in modelRef.propertyMappings" :key="index" :header="item.source ? getDuerOSProperties(item.source).find(i => i.id === item.source)?.name : `属性映射${index + 1}`">
|
||||||
|
<template #extra><AIcon type="DeleteOutlined" @click="delPropertyItem(index)" /></template>
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="DuerOS属性" :name="['propertyMappings', index, 'source']" :rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请选择DuerOS属性',
|
||||||
|
}">
|
||||||
|
<a-select placeholder="请选择DuerOS属性" v-model:value="item.source" show-search :filter-option="filterOption">
|
||||||
|
<a-select-option v-for="i in getDuerOSProperties(item.source)" :key="i.id" :value="i.id">{{i.name}}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="平台属性" :name="['propertyMappings', index, 'target']" :rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请选择平台属性',
|
||||||
|
}">
|
||||||
|
<a-select placeholder="请选择平台属性" v-model:value="item.target" mode="tags" show-search :filter-option="filterOption">
|
||||||
|
<a-select-option v-for="i in getProductProperties(item.target)" :key="i.id" :value="item.id">{{i.name}}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-button type="dashed" style="width: 100%; margin-top: 10px" @click="addPropertyItem">
|
||||||
|
<AIcon
|
||||||
|
type="PlusOutlined"
|
||||||
|
style="margin-left: 2px;" />新增属性
|
||||||
|
</a-button>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24" style="margin-top: 20px">
|
||||||
|
<a-form-item label="说明" name="description" :rules="{
|
||||||
|
max: 200,
|
||||||
|
message: '最多输入200个字符',
|
||||||
|
}">
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="modelRef.description"
|
||||||
|
placeholder="请输入说明"
|
||||||
|
showCount
|
||||||
|
:maxlength="200"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
<div v-if="type === 'edit'">
|
||||||
|
<a-button :loading="loading" type="primary" @click="saveBtn">保存</a-button>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<Doc />
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
</page-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Doc from './doc.vue'
|
||||||
|
import Command from './command/index.vue'
|
||||||
|
import { queryProductList, queryTypes, savePatch, detail } from '@/api/northbound/dueros'
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
|
||||||
|
const modelRef = reactive({
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
applianceType: undefined,
|
||||||
|
productName: undefined,
|
||||||
|
actionMappings: [{
|
||||||
|
actionType: undefined,
|
||||||
|
action: undefined,
|
||||||
|
command: {
|
||||||
|
messageType: undefined,
|
||||||
|
message: {
|
||||||
|
properties: undefined,
|
||||||
|
functionId: undefined,
|
||||||
|
inputs: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
propertyMappings: [{
|
||||||
|
source: undefined,
|
||||||
|
target: []
|
||||||
|
}],
|
||||||
|
description: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
const addItem = () => {
|
||||||
|
modelRef.actionMappings.push({
|
||||||
|
actionType: undefined,
|
||||||
|
action: undefined,
|
||||||
|
command: {
|
||||||
|
messageType: undefined,
|
||||||
|
message: {
|
||||||
|
properties: undefined,
|
||||||
|
functionId: undefined,
|
||||||
|
inputs: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const productList = ref<Record<string, any>[]>([])
|
||||||
|
const typeList = ref<Record<string, any>[]>([])
|
||||||
|
const command = ref([])
|
||||||
|
const loading = ref<boolean>(false)
|
||||||
|
const type = ref<'edit' | 'view'>('view')
|
||||||
|
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const delItem = (index: number) => {
|
||||||
|
modelRef.actionMappings.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addPropertyItem = () => {
|
||||||
|
modelRef.propertyMappings.push({
|
||||||
|
source: undefined,
|
||||||
|
target: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const delPropertyItem = (index: number) => {
|
||||||
|
modelRef.propertyMappings.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const productChange = (value: string) => {
|
||||||
|
modelRef.propertyMappings = modelRef.propertyMappings.map(item => {
|
||||||
|
return {source: item.source, target: []}
|
||||||
|
})
|
||||||
|
const item = productList.value.find(item => item.id === value)
|
||||||
|
if(item){
|
||||||
|
modelRef.productName = item.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeChange = () => {
|
||||||
|
modelRef.propertyMappings = modelRef.propertyMappings.map(item => {
|
||||||
|
return {source: undefined, target: item.target}
|
||||||
|
})
|
||||||
|
modelRef.actionMappings = modelRef.actionMappings.map(item => {
|
||||||
|
return {...item, action: undefined}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const findApplianceType = computed(() => {
|
||||||
|
if(!modelRef.applianceType) return
|
||||||
|
return typeList.value.find(item => item.id === modelRef.applianceType)
|
||||||
|
})
|
||||||
|
|
||||||
|
const findProductMetadata = computed(() => {
|
||||||
|
if(!modelRef.id) return
|
||||||
|
const _product = productList.value?.find((item: any) => item.id === modelRef.id)
|
||||||
|
return _product?.metadata && JSON.parse(_product.metadata || '{}')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 查询产品列表
|
||||||
|
const getProduct = async (id?: string) => {
|
||||||
|
const resp = await queryProductList(id)
|
||||||
|
if(resp.status === 200){
|
||||||
|
productList.value = (resp?.result as Record<string, any>[])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTypes = async () => {
|
||||||
|
const resp = await queryTypes()
|
||||||
|
if(resp.status === 200){
|
||||||
|
typeList.value = (resp?.result as Record<string, any>[])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDuerOSProperties = (val: string) => {
|
||||||
|
const arr = modelRef.propertyMappings.map(item => item?.source) || []
|
||||||
|
const checked = _.cloneDeep(arr)
|
||||||
|
const _index = checked.findIndex(i => i === val)
|
||||||
|
// 去掉重复的
|
||||||
|
checked.splice(_index, 1)
|
||||||
|
const targetList = findApplianceType.value?.properties;
|
||||||
|
const list = targetList?.filter((i: {id: string}) => !checked.includes(i?.id as any))
|
||||||
|
return list || []
|
||||||
|
}
|
||||||
|
|
||||||
|
const getProductProperties = (val: string[]) => {
|
||||||
|
const items = modelRef.propertyMappings.map((item: {target: string[]}) => item?.target.map(j => j)) || []
|
||||||
|
const checked = _.flatMap(items)
|
||||||
|
const _checked: any[] = []
|
||||||
|
checked.map(_item => {
|
||||||
|
if(!val.includes(_item)){
|
||||||
|
_checked.push(_item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const sourceList = findProductMetadata.value?.properties
|
||||||
|
const list = sourceList?.filter((i: { id: string }) => !_checked.includes(i.id))
|
||||||
|
return list || []
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTypesActions = (val: string) => {
|
||||||
|
const items = modelRef.actionMappings.map((item) => item?.action) || []
|
||||||
|
const checked = _.cloneDeep(items)
|
||||||
|
const _index = checked.findIndex(i => i === val)
|
||||||
|
checked.splice(_index, 1)
|
||||||
|
const actionsList = findApplianceType.value?.actions || []
|
||||||
|
const list = actionsList?.filter((i: { id: string, name: string }) => !checked.includes(i?.id as any))
|
||||||
|
return list || []
|
||||||
|
}
|
||||||
|
const saveBtn = async () => {
|
||||||
|
const tasks = []
|
||||||
|
for(let i = 0; i < command.value.length; i++){
|
||||||
|
const res = await (command.value[i] as any)?.saveBtn()
|
||||||
|
tasks.push(res)
|
||||||
|
if(!res) break
|
||||||
|
}
|
||||||
|
const data = await formRef.value.validate()
|
||||||
|
if(tasks.every(item => item) && data){
|
||||||
|
loading.value = true;
|
||||||
|
const resp = await savePatch(toRaw(modelRef));
|
||||||
|
loading.value = false;
|
||||||
|
if (resp.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
formRef.value.resetFields();
|
||||||
|
router.push('/iot/northbound/DuerOS/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
() => route.params?.id,
|
||||||
|
async (newId) => {
|
||||||
|
if(newId){
|
||||||
|
getProduct(newId as string)
|
||||||
|
getTypes()
|
||||||
|
if (newId === ':id') return;
|
||||||
|
const resp = await detail(newId as string)
|
||||||
|
const _data: any = resp.result;
|
||||||
|
if (_data) {
|
||||||
|
_data.applianceType = _data?.applianceType?.value;
|
||||||
|
}
|
||||||
|
Object.assign(modelRef, _data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{immediate: true, deep: true}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.query.type,
|
||||||
|
(newVal) => {
|
||||||
|
if(newVal){
|
||||||
|
type.value = newVal as 'edit' | 'view'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{immediate: true, deep: true}
|
||||||
|
);
|
||||||
|
</script>
|
|
@ -1,3 +0,0 @@
|
||||||
<template>
|
|
||||||
123
|
|
||||||
</template>
|
|
|
@ -1,69 +1,141 @@
|
||||||
<template>
|
<template>
|
||||||
<page-container>
|
<page-container>
|
||||||
<JTable
|
<Search :columns="columns" target="northbound-dueros" :params="params" />
|
||||||
|
<JTable
|
||||||
|
ref="instanceRef"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:request="request"
|
:request="query"
|
||||||
|
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
|
||||||
|
:params="params"
|
||||||
>
|
>
|
||||||
<template #headerTitle>
|
<template #headerTitle>
|
||||||
<a-button type="primary" @click="add">新增</a-button>
|
<a-space>
|
||||||
|
<a-button type="primary" @click="handleAdd">新增</a-button>
|
||||||
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
<template #card="slotProps">
|
<template #card="slotProps">
|
||||||
<CardBox
|
<CardBox
|
||||||
:value="slotProps"
|
:value="slotProps"
|
||||||
@click="handleClick"
|
:actions="getActions(slotProps, 'card')"
|
||||||
:actions="getActions(slotProps)"
|
v-bind="slotProps"
|
||||||
v-bind="slotProps"
|
:status="slotProps.state?.value"
|
||||||
:status="slotProps.state ? 'success' : 'error'"
|
:statusText="slotProps.state?.text"
|
||||||
|
:statusNames="{
|
||||||
|
enabled: 'success',
|
||||||
|
disabled: 'error'
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<template #img>
|
<template #img>
|
||||||
<slot name="img">
|
<slot name="img">
|
||||||
<img :src="getImage('/device-product.png')" />
|
<img
|
||||||
|
:src="
|
||||||
|
getImage('/cloud/dueros.png')
|
||||||
|
"
|
||||||
|
/>
|
||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<h3>{{slotProps.name}}</h3>
|
<h3
|
||||||
<a-row>
|
class="card-item-content-title"
|
||||||
|
@click.stop="handleView(slotProps.id)"
|
||||||
|
>
|
||||||
|
{{ slotProps.name }}
|
||||||
|
</h3>
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="12">
|
||||||
|
<div class="card-item-content-text">
|
||||||
|
产品
|
||||||
|
</div>
|
||||||
|
<div>{{ slotProps?.productName }}</div>
|
||||||
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<div class="card-item-content-text">
|
<div class="card-item-content-text">
|
||||||
设备类型
|
设备类型
|
||||||
</div>
|
</div>
|
||||||
<div>直连设备</div>
|
<div>{{ slotProps?.applianceType?.text }}</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</template>
|
</template>
|
||||||
<template #actions="item">
|
<template #actions="item">
|
||||||
<a-popconfirm v-if="item.popConfirm" v-bind="item.popConfirm">
|
<a-tooltip
|
||||||
<a-button :disabled="item.disabled">
|
v-bind="item.tooltip"
|
||||||
<DeleteOutlined v-if="item.key === 'delete'" />
|
:title="item.disabled && item.tooltip.title"
|
||||||
<template v-else>
|
>
|
||||||
<AIcon :type="item.icon" />
|
<a-popconfirm
|
||||||
<span>{{ item.text }}</span>
|
v-if="item.popConfirm"
|
||||||
</template>
|
v-bind="item.popConfirm"
|
||||||
</a-button>
|
:disabled="item.disabled"
|
||||||
</a-popconfirm>
|
>
|
||||||
<template v-else>
|
<a-button :disabled="item.disabled">
|
||||||
<a-button :disabled="item.disabled">
|
<AIcon
|
||||||
<DeleteOutlined v-if="item.key === 'delete'" />
|
type="DeleteOutlined"
|
||||||
<template v-else>
|
v-if="item.key === 'delete'"
|
||||||
<AIcon :type="item.icon" />
|
/>
|
||||||
<span>{{ item.text }}</span>
|
<template v-else>
|
||||||
</template>
|
<AIcon :type="item.icon" />
|
||||||
</a-button>
|
<span>{{ item?.text }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<template v-else>
|
||||||
|
<a-button
|
||||||
|
:disabled="item.disabled"
|
||||||
|
@click="item.onClick"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="DeleteOutlined"
|
||||||
|
v-if="item.key === 'delete'"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<AIcon :type="item.icon" />
|
||||||
|
<span>{{ item?.text }}</span>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</CardBox>
|
</CardBox>
|
||||||
</template>
|
</template>
|
||||||
<template #id="slotProps">
|
<template #state="slotProps">
|
||||||
<a>{{slotProps.id}}</a>
|
<a-badge
|
||||||
|
:text="slotProps.state?.text"
|
||||||
|
:status="statusMap.get(slotProps.state?.value)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #applianceType="slotProps">
|
||||||
|
{{slotProps.applianceType.text}}
|
||||||
</template>
|
</template>
|
||||||
<template #action="slotProps">
|
<template #action="slotProps">
|
||||||
<a-space :size="16">
|
<a-space :size="16">
|
||||||
<a-tooltip v-for="i in getActions(slotProps)" :key="i.key" v-bind="i.tooltip">
|
<a-tooltip
|
||||||
<a-popconfirm v-if="i.popConfirm" v-bind="i.popConfirm">
|
v-for="i in getActions(slotProps, 'table')"
|
||||||
<a-button :disabled="i.disabled" style="padding: 0" type="link"><AIcon :type="i.icon" /></a-button>
|
: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-popconfirm>
|
||||||
<a-button style="padding: 0" type="link" v-else @click="i.onClick && i.onClick(slotProps)">
|
<a-button
|
||||||
<a-button :disabled="i.disabled" style="padding: 0" type="link"><AIcon :type="i.icon" /></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-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-space>
|
</a-space>
|
||||||
|
@ -73,13 +145,24 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { query } from '@/api/northbound/dueros'
|
import {
|
||||||
import type { ActionsType } from '@/components/Table/index.vue'
|
query,
|
||||||
|
_undeploy,
|
||||||
|
_deploy,
|
||||||
|
_delete
|
||||||
|
} from '@/api/northbound/dueros';
|
||||||
|
import type { ActionsType } from '@/components/Table/index.vue';
|
||||||
import { getImage } from '@/utils/comm';
|
import { getImage } from '@/utils/comm';
|
||||||
import { DeleteOutlined } from '@ant-design/icons-vue'
|
import { message } from 'ant-design-vue';
|
||||||
import { message } from "ant-design-vue";
|
|
||||||
|
|
||||||
const request = (data: any) => query({})
|
const router = useRouter();
|
||||||
|
const instanceRef = ref<Record<string, any>>({});
|
||||||
|
const params = ref<Record<string, any>>({});
|
||||||
|
const current = ref<Record<string, any>>({});
|
||||||
|
|
||||||
|
const statusMap = new Map();
|
||||||
|
statusMap.set('enabled', 'success');
|
||||||
|
statusMap.set('disabled', 'error');
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
|
@ -88,67 +171,146 @@ const columns = [
|
||||||
key: 'name',
|
key: 'name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: '产品名称',
|
||||||
dataIndex: 'id',
|
dataIndex: 'productName',
|
||||||
key: 'id',
|
key: 'productName',
|
||||||
scopedSlots: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '分类',
|
title: '设备类型',
|
||||||
dataIndex: 'classifiedName',
|
dataIndex: 'applianceType',
|
||||||
key: 'classifiedName',
|
key: 'applianceType',
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '说明',
|
||||||
|
dataIndex: 'describe',
|
||||||
|
key: 'describe',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'state',
|
||||||
|
key: 'state',
|
||||||
|
scopedSlots: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 250,
|
width: 250,
|
||||||
scopedSlots: true
|
scopedSlots: true,
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
const handleClick = (dt: any) => {
|
/**
|
||||||
|
* 新增
|
||||||
}
|
*/
|
||||||
|
const handleAdd = () => {
|
||||||
|
router.push('/iot/northbound/DuerOS/detail/:id');
|
||||||
|
};
|
||||||
|
|
||||||
const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
|
/**
|
||||||
if(!data){
|
* 查看
|
||||||
return []
|
*/
|
||||||
}
|
const handleView = (id: string) => {
|
||||||
return [
|
// router.push('/iot/northbound/DuerOS/detail/' + id);
|
||||||
|
router.push({
|
||||||
|
path: '/iot/northbound/DuerOS/detail/' + id,
|
||||||
|
query: {
|
||||||
|
type: 'view'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActions = (
|
||||||
|
data: Partial<Record<string, any>>,
|
||||||
|
type: 'card' | 'table',
|
||||||
|
): ActionsType[] => {
|
||||||
|
if (!data) return [];
|
||||||
|
const actions = [
|
||||||
{
|
{
|
||||||
key: 'edit',
|
key: 'view',
|
||||||
text: "编辑",
|
text: '查看',
|
||||||
tooltip: {
|
tooltip: {
|
||||||
title: '编辑'
|
title: '查看',
|
||||||
|
},
|
||||||
|
icon: 'EyeOutlined',
|
||||||
|
onClick: () => {
|
||||||
|
handleView(data.id);
|
||||||
},
|
},
|
||||||
icon: 'icon-rizhifuwu'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'import',
|
key: 'edit',
|
||||||
text: "导入",
|
text: '编辑',
|
||||||
tooltip: {
|
tooltip: {
|
||||||
title: '导入'
|
title: '编辑',
|
||||||
|
},
|
||||||
|
icon: 'EditOutlined',
|
||||||
|
onClick: () => {
|
||||||
|
router.push({
|
||||||
|
path: '/iot/northbound/DuerOS/detail/' + data.id,
|
||||||
|
query: {
|
||||||
|
type: 'edit'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'action',
|
||||||
|
text: data.state?.value !== 'disabled' ? '禁用' : '启用',
|
||||||
|
tooltip: {
|
||||||
|
title: data.state?.value !== 'disabled' ? '禁用' : '启用',
|
||||||
|
},
|
||||||
|
icon:
|
||||||
|
data.state.value !== 'notActive'
|
||||||
|
? 'StopOutlined'
|
||||||
|
: 'CheckCircleOutlined',
|
||||||
|
popConfirm: {
|
||||||
|
title: `确认${
|
||||||
|
data.state.value !== 'disabled' ? '禁用' : '启用'
|
||||||
|
}?`,
|
||||||
|
onConfirm: async () => {
|
||||||
|
let response = undefined;
|
||||||
|
if (data.state.value !== 'disabled') {
|
||||||
|
response = await _undeploy(data.id);
|
||||||
|
} else {
|
||||||
|
response = await _deploy(data.id);
|
||||||
|
}
|
||||||
|
if (response && response.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
instanceRef.value?.reload();
|
||||||
|
} else {
|
||||||
|
message.error('操作失败!');
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
disabled: true,
|
|
||||||
icon: 'icon-xiazai'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
text: "删除",
|
text: '删除',
|
||||||
|
disabled: data.state?.value !== 'disabled',
|
||||||
tooltip: {
|
tooltip: {
|
||||||
title: !!data?.state ? '正常的产品不能删除' : '删除'
|
title:
|
||||||
|
data.state.value !== 'disabled'
|
||||||
|
? '请先禁用该数据,再删除。'
|
||||||
|
: '删除',
|
||||||
},
|
},
|
||||||
popConfirm: {
|
popConfirm: {
|
||||||
title: '确认删除?'
|
title: '确认删除?',
|
||||||
|
onConfirm: async () => {
|
||||||
|
const resp = await _delete(data.id);
|
||||||
|
if (resp.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
instanceRef.value?.reload();
|
||||||
|
} else {
|
||||||
|
message.error('操作失败!');
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
icon: 'icon-huishouzhan'
|
icon: 'DeleteOutlined',
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
}
|
if (type === 'card')
|
||||||
|
return actions.filter((i: ActionsType) => i.key !== 'view');
|
||||||
const add = () => {
|
return actions;
|
||||||
// router.push(`/northbound/DuerOS/detail/:id`)
|
};
|
||||||
}
|
</script>
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:maskClosable="false"
|
||||||
|
width="650px"
|
||||||
|
destroyOnClose
|
||||||
|
v-model:visible="visible"
|
||||||
|
:title="props.title"
|
||||||
|
@ok="handleSave"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
:confirmLoading="loading"
|
||||||
|
>
|
||||||
|
<div style="margin-top: 10px">
|
||||||
|
<a-form
|
||||||
|
:layout="'vertical'"
|
||||||
|
ref="formRef"
|
||||||
|
:rules="rules"
|
||||||
|
:model="modelRef"
|
||||||
|
>
|
||||||
|
<a-form-item label="名称" name="name">
|
||||||
|
<a-input
|
||||||
|
v-model:value="modelRef.name"
|
||||||
|
placeholder="请输入名称"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="说明" name="describe">
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="modelRef.description"
|
||||||
|
placeholder="请输入说明"
|
||||||
|
showCount
|
||||||
|
:maxlength="200"
|
||||||
|
:rows="4"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { saveRule , modify } from '@/api/rule-engine/instance';
|
||||||
|
import { getImage } from '@/utils/comm';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
isAdd: {
|
||||||
|
type: Number,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const productList = ref<Record<string, any>[]>([]);
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
const visible = ref<boolean>(false);
|
||||||
|
const formRef = ref();
|
||||||
|
let id = ref<string>();
|
||||||
|
const modelRef = reactive({
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
max: 64,
|
||||||
|
message: '最多输入64个字符',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isAdd,
|
||||||
|
() => {},
|
||||||
|
{ immediate: true, deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
visible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
formRef.value
|
||||||
|
.validate()
|
||||||
|
.then(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
if (props.isAdd === 1) {
|
||||||
|
let resp = await saveRule(modelRef);
|
||||||
|
loading.value = false;
|
||||||
|
if (resp.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
emit('success');
|
||||||
|
formRef.value.resetFields();
|
||||||
|
visible.value = false;
|
||||||
|
}else{
|
||||||
|
message.error('操作失败')
|
||||||
|
}
|
||||||
|
}else if(props.isAdd === 2) {
|
||||||
|
let resp = await modify(id,modelRef);
|
||||||
|
loading.value = false;
|
||||||
|
if (resp.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
emit('success');
|
||||||
|
formRef.value.resetFields();
|
||||||
|
visible.value = false;
|
||||||
|
}else{
|
||||||
|
message.error('操作失败!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.log('error', err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const show = (data: any) => {
|
||||||
|
if (props.isAdd === 1) {
|
||||||
|
modelRef.name = '';
|
||||||
|
modelRef.description = '';
|
||||||
|
} else if (props.isAdd === 2) {
|
||||||
|
modelRef.name = data?.name;
|
||||||
|
modelRef.description = data?.description;
|
||||||
|
id = data.id
|
||||||
|
}
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
|
defineExpose({
|
||||||
|
show: show,
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,333 @@
|
||||||
|
<template>
|
||||||
|
<page-container>
|
||||||
|
<a-card>
|
||||||
|
<Search :columns="query.columns" target="device-instance" @search="handleSearch"></Search>
|
||||||
|
<JTable
|
||||||
|
:columns="columns"
|
||||||
|
:request="queryList"
|
||||||
|
ref="tableRef"
|
||||||
|
:defaultParams="{
|
||||||
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
|
}"
|
||||||
|
:params="params"
|
||||||
|
>
|
||||||
|
<template #headerTitle>
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="add"
|
||||||
|
><plus-outlined/>新增</a-button
|
||||||
|
>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<template #card="slotProps">
|
||||||
|
<CardBox
|
||||||
|
:value="slotProps"
|
||||||
|
:actions="getActions(slotProps, 'card')"
|
||||||
|
v-bind="slotProps"
|
||||||
|
:status="slotProps.state?.value"
|
||||||
|
:statusText="slotProps.state?.text"
|
||||||
|
:statusNames="{
|
||||||
|
started: 'success',
|
||||||
|
disable: 'error',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #img>
|
||||||
|
<slot name="img">
|
||||||
|
<img :src="getImage('/device-product.png')" />
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<h3 style="font-weight: 600">
|
||||||
|
{{ slotProps.name }}
|
||||||
|
</h3>
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="12">
|
||||||
|
<div class="rule-desc">
|
||||||
|
{{ slotProps.description }}
|
||||||
|
</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"
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
>
|
||||||
|
<a-button :disabled="item.disabled">
|
||||||
|
<AIcon
|
||||||
|
type="DeleteOutlined"
|
||||||
|
v-if="item.key === 'delete'"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<AIcon :type="item.icon" />
|
||||||
|
<span>{{ item?.text }}</span>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<template v-else>
|
||||||
|
<a-button
|
||||||
|
:disabled="item.disabled"
|
||||||
|
@click="item.onClick"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="DeleteOutlined"
|
||||||
|
v-if="item.key === 'delete'"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<AIcon :type="item.icon" />
|
||||||
|
<span>{{ item?.text }}</span>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</CardBox>
|
||||||
|
</template>
|
||||||
|
<template #state="slotProps">
|
||||||
|
<a-badge
|
||||||
|
:text="
|
||||||
|
slotProps.state?.value === 'started'
|
||||||
|
? '正常'
|
||||||
|
: '禁用'
|
||||||
|
"
|
||||||
|
:status="
|
||||||
|
slotProps.state?.value === 'started'
|
||||||
|
? 'success'
|
||||||
|
: 'error'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #action="slotProps">
|
||||||
|
<a-space :size="16">
|
||||||
|
<a-tooltip
|
||||||
|
v-for="i in getActions(slotProps)"
|
||||||
|
:key="i.key"
|
||||||
|
v-bind="i.tooltip"
|
||||||
|
>
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="i.popConfirm"
|
||||||
|
v-bind="i.popConfirm"
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
<!-- 新增、编辑 -->
|
||||||
|
<Save
|
||||||
|
ref="saveRef"
|
||||||
|
:isAdd="isAdd"
|
||||||
|
:title="title"
|
||||||
|
@success="refresh"
|
||||||
|
/>
|
||||||
|
</a-card>
|
||||||
|
</page-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import JTable from '@/components/Table';
|
||||||
|
import type { InstanceItem } from './typings';
|
||||||
|
import { queryList , startRule , stopRule , deleteRule} from '@/api/rule-engine/instance';
|
||||||
|
import type { ActionsType } from '@/components/Table/index.vue';
|
||||||
|
import { getImage } from '@/utils/comm';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import Save from './Save/index.vue';
|
||||||
|
const params = ref<Record<string, any>>({});
|
||||||
|
let isAdd = ref<number>(0);
|
||||||
|
let title = ref<string>('');
|
||||||
|
let saveRef = ref();
|
||||||
|
let currentForm = ref();
|
||||||
|
const tableRef = ref<Record<string, any>>({});
|
||||||
|
const query = {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'state',
|
||||||
|
key: 'state',
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: '正常',
|
||||||
|
value: 'started',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '禁用',
|
||||||
|
value: 'disable',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'state',
|
||||||
|
key: 'state',
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '说明',
|
||||||
|
dataIndex: 'describe',
|
||||||
|
key: 'describe',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 250,
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const getActions = (
|
||||||
|
data: Partial<Record<string, any>>,
|
||||||
|
type?: 'card' | 'table',
|
||||||
|
): ActionsType[] => {
|
||||||
|
if (!data) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
key: 'edit',
|
||||||
|
text: '编辑',
|
||||||
|
tooltip: {
|
||||||
|
title: '编辑',
|
||||||
|
},
|
||||||
|
|
||||||
|
icon: 'EditOutlined',
|
||||||
|
onClick: () => {
|
||||||
|
title.value = '编辑';
|
||||||
|
isAdd.value = 2;
|
||||||
|
nextTick(() => {
|
||||||
|
saveRef.value.show(data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'view',
|
||||||
|
text: '查看',
|
||||||
|
tooltip: {
|
||||||
|
title: '查看',
|
||||||
|
},
|
||||||
|
icon: 'EyeOutlined',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'action',
|
||||||
|
text: data.state?.value !== 'disable' ? '禁用' : '启用',
|
||||||
|
tooltip: {
|
||||||
|
title: data.state?.value !== 'disable' ? '禁用' : '启用',
|
||||||
|
},
|
||||||
|
icon: data.state?.value !== 'disable' ? 'StopOutlined' : 'CheckCircleOutlined',
|
||||||
|
popConfirm: {
|
||||||
|
title: `确认${data.state !== 'disable' ? '禁用' : '启用'}?`,
|
||||||
|
onConfirm: async () => {
|
||||||
|
let response = undefined;
|
||||||
|
if (data.state?.value !== 'started') {
|
||||||
|
response = await startRule(data.id);
|
||||||
|
} else {
|
||||||
|
response = await stopRule(data.id);
|
||||||
|
}
|
||||||
|
if (response && response.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
tableRef.value?.reload();
|
||||||
|
} else {
|
||||||
|
message.error('操作失败!');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'delete',
|
||||||
|
text: '删除',
|
||||||
|
disabled: data?.state?.value !== 'disable',
|
||||||
|
tooltip: {
|
||||||
|
title:
|
||||||
|
data?.state?.value !== 'disable'
|
||||||
|
? '请先禁用再删除'
|
||||||
|
: '删除',
|
||||||
|
},
|
||||||
|
popConfirm: {
|
||||||
|
title: '确认删除?',
|
||||||
|
onConfirm: async () => {
|
||||||
|
const resp = await deleteRule(data.id);
|
||||||
|
if (resp.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
tableRef.value?.reload();
|
||||||
|
} else {
|
||||||
|
message.error('操作失败!');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: 'DeleteOutlined',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
if (type === 'card')
|
||||||
|
return actions.filter((i: ActionsType) => i.key !== 'view');
|
||||||
|
return actions;
|
||||||
|
};
|
||||||
|
const add = () => {
|
||||||
|
isAdd.value = 1;
|
||||||
|
title.value = '新增';
|
||||||
|
nextTick(() => {
|
||||||
|
saveRef.value.show(currentForm.value);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 刷新数据
|
||||||
|
*/
|
||||||
|
const refresh = () => {
|
||||||
|
tableRef.value?.reload();
|
||||||
|
};
|
||||||
|
const handleSearch = (e: any) => {
|
||||||
|
console.log(e);
|
||||||
|
params.value = e;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.rule-desc {
|
||||||
|
white-space: nowrap; /*强制在同一行内显示所有文本,直到文本结束或者遭遇br标签对象才换行。*/
|
||||||
|
overflow: hidden; /*超出部分隐藏*/
|
||||||
|
text-overflow: ellipsis; /*隐藏部分以省略号代替*/
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,15 @@
|
||||||
|
type InstanceItem = {
|
||||||
|
createTime: number;
|
||||||
|
modelId: string;
|
||||||
|
modelMeta: string;
|
||||||
|
modelType: string;
|
||||||
|
modelVersion: number;
|
||||||
|
description?: string;
|
||||||
|
state: {
|
||||||
|
text: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
} & {
|
||||||
|
id:string,
|
||||||
|
name:string
|
||||||
|
}
|
|
@ -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,7 +10,7 @@
|
||||||
<search-outlined />
|
<search-outlined />
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-button type="primary" @click="openDialog" class="add-btn">
|
<a-button type="primary" @click="openDialog()" class="add-btn">
|
||||||
新增
|
新增
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-tree
|
<a-tree
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
>
|
>
|
||||||
<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">
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
<EditDepartmentDialog
|
<EditDepartmentDialog
|
||||||
:tree-data="sourceTree"
|
:tree-data="sourceTree"
|
||||||
ref="editDialogRef"
|
ref="editDialogRef"
|
||||||
@refresh="getTree"
|
@refresh="refresh"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -84,19 +84,15 @@ import {
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
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 +115,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 +163,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>
|
||||||
|
|
|
@ -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>
|
|
@ -125,6 +125,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"
|
||||||
|
@ -155,14 +156,17 @@ 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 emits = defineEmits(['update:bindBool']);
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
parentId: string;
|
parentId: string;
|
||||||
|
bindBool: boolean;
|
||||||
}>();
|
}>();
|
||||||
const query = {
|
const query = {
|
||||||
columns: [
|
columns: [
|
||||||
|
@ -186,6 +190,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 +318,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 +436,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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -125,10 +125,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,6 +138,7 @@
|
||||||
asset-type="product"
|
asset-type="product"
|
||||||
@confirm="table.refresh"
|
@confirm="table.refresh"
|
||||||
/>
|
/>
|
||||||
|
<NextDialog ref="nextDialogRef" @confirm="emits('openDeviceBind')" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -149,6 +151,7 @@ import {
|
||||||
} from '@ant-design/icons-vue';
|
} 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 +164,7 @@ 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 emits = defineEmits(['openDeviceBind'])
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
parentId: string;
|
parentId: string;
|
||||||
}>();
|
}>();
|
||||||
|
@ -196,16 +200,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 +279,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,
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
// 整理参数并获取数据
|
// 整理参数并获取数据
|
||||||
|
@ -350,6 +367,7 @@ const table = {
|
||||||
},
|
},
|
||||||
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 +405,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>
|
||||||
|
|
||||||
|
|
|
@ -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,226 @@
|
||||||
<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>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
@click="table.openDialog"
|
||||||
|
style="margin-right: 10px"
|
||||||
|
>
|
||||||
|
<AIcon type="PlusOutlined" />绑定用户
|
||||||
|
</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
title="是否解除绑定"
|
||||||
|
ok-text="确定"
|
||||||
|
cancel-text="取消"
|
||||||
|
@confirm="table.unBind()"
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
><AIcon type="DisconnectOutlined" />批量解绑</a-button
|
||||||
|
>
|
||||||
|
</a-popconfirm>
|
||||||
|
</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-popconfirm
|
||||||
|
title="是否解除绑定"
|
||||||
|
ok-text="确定"
|
||||||
|
cancel-text="取消"
|
||||||
|
@confirm="table.unBind(slotProps)"
|
||||||
|
>
|
||||||
|
<a-button style="padding: 0" type="link">
|
||||||
|
<AIcon type="DisconnectOutlined" />
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</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 AddBindUserDialog from './components/addBindUserDialog.vue';
|
||||||
|
import { getBindUserList_api, unBindUser_api } from '@/api/system/department';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
|
@ -0,0 +1,425 @@
|
||||||
|
<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>
|
||||||
|
<span
|
||||||
|
class="add-item"
|
||||||
|
@click="form.clickAddItem('roleIdList', 'Role')"
|
||||||
|
><AIcon type="PlusOutlined"
|
||||||
|
/></span>
|
||||||
|
</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>
|
||||||
|
<span
|
||||||
|
class="add-item"
|
||||||
|
@click="
|
||||||
|
form.clickAddItem('orgIdList', 'Department')
|
||||||
|
"
|
||||||
|
><AIcon type="PlusOutlined"
|
||||||
|
/></span>
|
||||||
|
</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 { FormInstance, message, TreeProps } 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 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;
|
||||||
|
}
|
||||||
|
.add-item {
|
||||||
|
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,296 @@
|
||||||
|
<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
|
||||||
|
>
|
||||||
|
</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>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</JTable>
|
||||||
|
|
||||||
|
<div class="dialogs">
|
||||||
|
<EditUserDialog ref="editDialogRef" @confirm="table.refresh" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="UserMange">
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue