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)
|
||||
|
|
@ -6,3 +6,66 @@ import server from '@/utils/request'
|
|||
* @returns
|
||||
*/
|
||||
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 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 getDeviceList_api = (data: object) => server.post(`/device/instance/_query`, data);
|
||||
// 根据产品的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`);
|
||||
|
||||
|
@ -25,3 +27,12 @@ export const bindDeviceOrProductList_api = (type: 'device' | 'product', data: ob
|
|||
export const unBindDeviceOrProduct_api = (type: 'device' | 'product', data: object) => server.post(`/assets/unbind/${type}`, data);
|
||||
// 批量更新权限
|
||||
export const 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 {
|
||||
width: 0;
|
||||
height: 100%;
|
||||
height: 123px;
|
||||
display: flex;
|
||||
flex-grow: .7;
|
||||
align-items: flex-end;
|
||||
|
|
|
@ -24,10 +24,12 @@
|
|||
:footer="onlineFooter"
|
||||
:value="onlineToday"
|
||||
>
|
||||
<BarChart
|
||||
<!-- <BarChart
|
||||
:chartXData="barChartXData"
|
||||
:chartYData="barChartYData"
|
||||
></BarChart> </TopCard
|
||||
></BarChart> -->
|
||||
<Charts :options="onlineOptions"></Charts>
|
||||
</TopCard
|
||||
></a-col>
|
||||
<a-col :span="6"
|
||||
><TopCard
|
||||
|
@ -35,10 +37,7 @@
|
|||
:footer="messageFooter"
|
||||
:value="dayMessage"
|
||||
>
|
||||
<LineChart
|
||||
:chartXData="lineChartXData"
|
||||
:chartYData="lineChartYData"
|
||||
></LineChart> </TopCard
|
||||
<Charts :options="TodayDevOptions"></Charts> </TopCard
|
||||
></a-col>
|
||||
</a-row>
|
||||
<a-row :span="24">
|
||||
|
@ -55,7 +54,7 @@
|
|||
</template>
|
||||
</Guide>
|
||||
<div class="message-chart">
|
||||
<MessageChart :x="messageChartXData" :y="messageChartYData" :maxY="messageMaxChartYData"></MessageChart>
|
||||
<Charts :options="devMegOptions"></Charts>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
|
@ -74,11 +73,9 @@
|
|||
</page-container>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import BarChart from './components/BarChart.vue';
|
||||
import LineChart from './components/LineChart.vue';
|
||||
import TimeSelect from './components/TimeSelect.vue';
|
||||
import Charts from './components/Charts.vue'
|
||||
import Guide from './components/Guide.vue';
|
||||
import MessageChart from './components/messageChart.vue';
|
||||
import {
|
||||
productCount,
|
||||
deviceCount,
|
||||
|
@ -130,13 +127,12 @@ let messageFooter = ref<Footer[]>([
|
|||
value: 0,
|
||||
},
|
||||
]);
|
||||
let lineChartYData = ref<any[]>([]);
|
||||
let lineChartXData = ref<any[]>([]);
|
||||
let barChartXData = ref<any[]>([]);
|
||||
let barChartYData = ref<any[]>([]);
|
||||
let messageChartXData = ref<any[]>([]);
|
||||
let messageChartYData = ref<any[]>([]);
|
||||
let messageMaxChartYData = ref<number>();
|
||||
let onlineOptions = ref<any>({});
|
||||
let TodayDevOptions = ref<any>({});
|
||||
let devMegOptions = ref<any>({});
|
||||
const quickBtnList = [
|
||||
{ label: '昨日', value: 'yesterday' },
|
||||
{ label: '近一周', value: 'week' },
|
||||
|
@ -215,13 +211,165 @@ const getOnline = () => {
|
|||
const x = res.result
|
||||
.map((item: any) => item.data.timeString)
|
||||
.reverse();
|
||||
barChartXData.value = x;
|
||||
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];
|
||||
}
|
||||
});
|
||||
};
|
||||
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();
|
||||
//今日设备消息量
|
||||
const getDevice = () => {
|
||||
|
@ -279,8 +427,7 @@ const getDevice = () => {
|
|||
);
|
||||
const x = today.map((item: any) => item.data.timeString).reverse();
|
||||
const y = today.map((item: any) => item.data.value).reverse();
|
||||
lineChartXData.value = x;
|
||||
lineChartYData.value = y;
|
||||
setTodayDevChartOption(x,y);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -322,15 +469,16 @@ const getEcharts = (data: any) => {
|
|||
},
|
||||
]).then((res:any) => {
|
||||
if (res.status === 200) {
|
||||
messageChartXData.value = res.result
|
||||
const x = res.result
|
||||
.map((item: any) =>
|
||||
_time === '1h'
|
||||
? `${item.data.timeString}时`
|
||||
: item.data.timeString,
|
||||
)
|
||||
.reverse();
|
||||
messageChartYData.value = res.result.map((item: any) => item.data.value).reverse();
|
||||
messageMaxChartYData.value = Math.max.apply(null, messageChartYData.value.length ? messageChartYData.value : [0]);
|
||||
const y = res.result.map((item: any) => item.data.value).reverse();
|
||||
const maxY = Math.max.apply(null, messageChartYData.value.length ? messageChartYData.value : [0]);
|
||||
setDevMesChartOption(x,y,maxY);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -16,9 +16,6 @@
|
|||
</a-button>
|
||||
</template>
|
||||
</JTable>
|
||||
<a-button type="link" @click="detail(slotProps)">
|
||||
<AIcon type="SearchOutlined" />
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -281,7 +281,7 @@ const api = ref<string>('');
|
|||
const type = ref<string>('');
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('online', 'processing');
|
||||
statusMap.set('online', 'success');
|
||||
statusMap.set('offline', 'error');
|
||||
statusMap.set('notActive', 'warning');
|
||||
|
||||
|
|
|
@ -1,163 +1,170 @@
|
|||
<template>
|
||||
<a-card class="device-product">
|
||||
<Search
|
||||
:columns="query.columns"
|
||||
target="product-manage"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JTable
|
||||
:columns="columns"
|
||||
:request="queryProductList"
|
||||
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-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"
|
||||
<page-container>
|
||||
<a-card class="device-product">
|
||||
<Search
|
||||
:columns="query.columns"
|
||||
target="product-manage"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JTable
|
||||
:columns="columns"
|
||||
:request="queryProductList"
|
||||
ref="tableRef"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
}"
|
||||
:params="params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="add"
|
||||
><plus-outlined />新增</a-button
|
||||
>
|
||||
{{ 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-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 }}
|
||||
</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
|
||||
v-bind="item.tooltip"
|
||||
:title="item.disabled && item.tooltip.title"
|
||||
v-for="i in getActions(slotProps)"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="item.popConfirm"
|
||||
v-bind="item.popConfirm"
|
||||
:disabled="item.disabled"
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
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>
|
||||
: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>
|
||||
</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
|
||||
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>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
<!-- 新增、编辑 -->
|
||||
<Save
|
||||
ref="saveRef"
|
||||
:isAdd="isAdd"
|
||||
:title="title"
|
||||
@success="refresh"
|
||||
/>
|
||||
</a-card>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -195,7 +202,7 @@ import Save from './Save/index.vue';
|
|||
const router = useRouter();
|
||||
const isAdd = ref<number>(0);
|
||||
const title = ref<string>('');
|
||||
const params = <Record<string, any>>{};
|
||||
const params = ref<Record<string, any>>({});
|
||||
const statusMap = new Map();
|
||||
statusMap.set(1, 'success');
|
||||
statusMap.set(0, 'error');
|
||||
|
|
|
@ -45,11 +45,9 @@
|
|||
<div class="card-item-content">
|
||||
<h3
|
||||
@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
|
||||
}}</a>
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
<a-row class="card-item-content-box">
|
||||
<a-col
|
||||
|
@ -402,9 +400,9 @@ const handleSearch = (e: any) => {
|
|||
min-height: 100px;
|
||||
|
||||
.card-item-content-title-a {
|
||||
// color: #000 !important;
|
||||
color: #1890ff !important;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
overflow: hidden; //超出的文本隐藏
|
||||
text-overflow: ellipsis; //溢出用省略号显示
|
||||
white-space: nowrap; //溢出不换行
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
</template>
|
||||
<template #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 }}
|
||||
</h3>
|
||||
<a-row class="card-item-content-box">
|
||||
|
@ -299,9 +299,9 @@ const handleSearch = (e: any) => {
|
|||
min-height: 100px;
|
||||
|
||||
.card-item-content-title-a {
|
||||
// color: #000 !important;
|
||||
color: #000 !important;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
overflow: hidden; //超出的文本隐藏
|
||||
text-overflow: ellipsis; //溢出用省略号显示
|
||||
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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="CertificateDetail">
|
||||
<script lang="ts" setup name="StreamDetail">
|
||||
import { message, Form } from 'ant-design-vue';
|
||||
import { queryProviders, queryDetail, save, update } from '@/api/media/stream';
|
||||
import type { FormInstance } from 'ant-design-vue';
|
||||
|
|
|
@ -45,12 +45,12 @@
|
|||
</template>
|
||||
<template #content>
|
||||
<div class="card-item-content">
|
||||
<a
|
||||
<h3
|
||||
@click="handlEye(slotProps.id)"
|
||||
class="card-item-content-title-a"
|
||||
class="card-item-content-title card-item-content-title-a"
|
||||
>
|
||||
{{ slotProps.name }}
|
||||
</a>
|
||||
</h3>
|
||||
<a-row class="card-item-content-box">
|
||||
<a-col
|
||||
:span="8"
|
||||
|
@ -149,7 +149,7 @@
|
|||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
<script lang="ts" setup name="AccessConfigPage">
|
||||
<script lang="ts" setup name="StreamPage">
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { query, remove, disable, enalbe } from '@/api/media/stream';
|
||||
|
@ -300,7 +300,7 @@ const handleSearch = (e: any) => {
|
|||
.card-item-content-title-a {
|
||||
// color: #000 !important;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
overflow: hidden; //超出的文本隐藏
|
||||
text-overflow: ellipsis; //溢出用省略号显示
|
||||
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>
|
||||
<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>
|
||||
|
||||
<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';
|
||||
|
||||
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>
|
||||
<page-container>
|
||||
<Search :columns="columns" target="northbound-dueros" :params="params" />
|
||||
<JTable
|
||||
ref="instanceRef"
|
||||
:columns="columns"
|
||||
:request="request"
|
||||
:request="query"
|
||||
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
|
||||
:params="params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-button type="primary" @click="add">新增</a-button>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleAdd">新增</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value="slotProps"
|
||||
@click="handleClick"
|
||||
:actions="getActions(slotProps)"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
v-bind="slotProps"
|
||||
:status="slotProps.state ? 'success' : 'error'"
|
||||
:status="slotProps.state?.value"
|
||||
:statusText="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
enabled: 'success',
|
||||
disabled: 'error'
|
||||
}"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img :src="getImage('/device-product.png')" />
|
||||
<img
|
||||
:src="
|
||||
getImage('/cloud/dueros.png')
|
||||
"
|
||||
/>
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3>{{slotProps.name}}</h3>
|
||||
<a-row>
|
||||
<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?.productName }}</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
</div>
|
||||
<div>直连设备</div>
|
||||
<div>{{ slotProps?.applianceType?.text }}</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<a-popconfirm v-if="item.popConfirm" v-bind="item.popConfirm">
|
||||
<a-button :disabled="item.disabled">
|
||||
<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">
|
||||
<DeleteOutlined v-if="item.key === 'delete'" />
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</template>
|
||||
<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 #id="slotProps">
|
||||
<a>{{slotProps.id}}</a>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
:text="slotProps.state?.text"
|
||||
:status="statusMap.get(slotProps.state?.value)"
|
||||
/>
|
||||
</template>
|
||||
<template #applianceType="slotProps">
|
||||
{{slotProps.applianceType.text}}
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip v-for="i in getActions(slotProps)" :key="i.key" v-bind="i.tooltip">
|
||||
<a-popconfirm v-if="i.popConfirm" v-bind="i.popConfirm">
|
||||
<a-button :disabled="i.disabled" style="padding: 0" type="link"><AIcon :type="i.icon" /></a-button>
|
||||
<a-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
|
||||
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>
|
||||
|
@ -73,13 +145,24 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { query } from '@/api/northbound/dueros'
|
||||
import type { ActionsType } from '@/components/Table/index.vue'
|
||||
import {
|
||||
query,
|
||||
_undeploy,
|
||||
_deploy,
|
||||
_delete
|
||||
} from '@/api/northbound/dueros';
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
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 = [
|
||||
{
|
||||
|
@ -88,67 +171,146 @@ const columns = [
|
|||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
scopedSlots: true
|
||||
title: '产品名称',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
},
|
||||
{
|
||||
title: '分类',
|
||||
dataIndex: 'classifiedName',
|
||||
key: 'classifiedName',
|
||||
title: '设备类型',
|
||||
dataIndex: 'applianceType',
|
||||
key: 'applianceType',
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'describe',
|
||||
key: 'describe',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 250,
|
||||
scopedSlots: true
|
||||
}
|
||||
]
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
|
||||
const handleClick = (dt: any) => {
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
const handleAdd = () => {
|
||||
router.push('/iot/northbound/DuerOS/detail/:id');
|
||||
};
|
||||
|
||||
}
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
const handleView = (id: string) => {
|
||||
// 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>>): ActionsType[] => {
|
||||
if(!data){
|
||||
return []
|
||||
}
|
||||
return [
|
||||
const getActions = (
|
||||
data: Partial<Record<string, any>>,
|
||||
type: 'card' | 'table',
|
||||
): ActionsType[] => {
|
||||
if (!data) return [];
|
||||
const actions = [
|
||||
{
|
||||
key: 'edit',
|
||||
text: "编辑",
|
||||
key: 'view',
|
||||
text: '查看',
|
||||
tooltip: {
|
||||
title: '编辑'
|
||||
title: '查看',
|
||||
},
|
||||
icon: 'EyeOutlined',
|
||||
onClick: () => {
|
||||
handleView(data.id);
|
||||
},
|
||||
icon: 'icon-rizhifuwu'
|
||||
},
|
||||
{
|
||||
key: 'import',
|
||||
text: "导入",
|
||||
key: 'edit',
|
||||
text: '编辑',
|
||||
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',
|
||||
text: "删除",
|
||||
text: '删除',
|
||||
disabled: data.state?.value !== 'disabled',
|
||||
tooltip: {
|
||||
title: !!data?.state ? '正常的产品不能删除' : '删除'
|
||||
title:
|
||||
data.state.value !== 'disabled'
|
||||
? '请先禁用该数据,再删除。'
|
||||
: '删除',
|
||||
},
|
||||
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'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const add = () => {
|
||||
// router.push(`/northbound/DuerOS/detail/:id`)
|
||||
}
|
||||
|
||||
icon: 'DeleteOutlined',
|
||||
},
|
||||
];
|
||||
if (type === 'card')
|
||||
return actions.filter((i: ActionsType) => i.key !== 'view');
|
||||
return actions;
|
||||
};
|
||||
</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" />
|
||||
</div>
|
||||
|
||||
<Search :columns="query.columns" @search="query.search" />
|
||||
<Search :columns="props.queryColumns" @search="query.search" />
|
||||
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
|
@ -118,6 +118,7 @@ import { message } from 'ant-design-vue';
|
|||
|
||||
const emits = defineEmits(['confirm']);
|
||||
const props = defineProps<{
|
||||
queryColumns: any[];
|
||||
parentId: string;
|
||||
allPermission: dictType;
|
||||
assetType: 'product' | 'device';
|
||||
|
@ -139,7 +140,6 @@ const dialog = {
|
|||
permission: item.selectPermissions,
|
||||
}));
|
||||
|
||||
// console.log(params);
|
||||
dialog.loading.value = true;
|
||||
bindDeviceOrProductList_api(props.assetType, params)
|
||||
.then(() => {
|
||||
|
@ -334,6 +334,14 @@ const table: any = {
|
|||
data.forEach((item) => {
|
||||
item.permissionList = permissionObj[item.id];
|
||||
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({
|
||||
|
|
|
@ -117,8 +117,8 @@ const form = reactive({
|
|||
form.loading = true;
|
||||
const api = form.data.id ? updateDepartment_api : addDepartment_api;
|
||||
api(form.data)
|
||||
.then(() => {
|
||||
emits('refresh');
|
||||
.then((resp:any) => {
|
||||
emits('refresh',resp.result.id);
|
||||
dialog.changeVisible(false);
|
||||
})
|
||||
.finally(() => (form.loading = false));
|
||||
|
|
|
@ -46,7 +46,6 @@ const dialog = {
|
|||
},
|
||||
// 控制弹窗的打开与关闭
|
||||
changeVisible: (ids: string[], permissionList: string[]) => {
|
||||
console.log(ids, permissionList);
|
||||
form.permission = [...permissionList];
|
||||
form.assetIdList = ids;
|
||||
options.value = setOptions(permissionList);
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<search-outlined />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-button type="primary" @click="openDialog" class="add-btn">
|
||||
<a-button type="primary" @click="openDialog()" class="add-btn">
|
||||
新增
|
||||
</a-button>
|
||||
<a-tree
|
||||
|
@ -21,7 +21,7 @@
|
|||
>
|
||||
<template #title="{ name, data }">
|
||||
<span>{{ name }}</span>
|
||||
<span class="func-btns">
|
||||
<span class="func-btns" @click="(e) => e.stopPropagation()">
|
||||
<a-tooltip>
|
||||
<template #title>编辑</template>
|
||||
<a-button style="padding: 0" type="link">
|
||||
|
@ -65,7 +65,7 @@
|
|||
<EditDepartmentDialog
|
||||
:tree-data="sourceTree"
|
||||
ref="editDialogRef"
|
||||
@refresh="getTree"
|
||||
@refresh="refresh"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -84,19 +84,15 @@ import {
|
|||
} from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const save = useRoute().query.save;
|
||||
const emits = defineEmits(['change']);
|
||||
const searchValue = ref('');// 搜索内容
|
||||
const searchValue = ref(''); // 搜索内容
|
||||
const loading = ref<boolean>(false); // 数据加载状态
|
||||
const sourceTree = ref<any[]>([]); // 源数据
|
||||
const treeMap = new Map(); // 数据的map版本
|
||||
const treeData = ref<any[]>([]); // 展示的数据
|
||||
const selectedKeys = ref<string[]>([]); // 当前选中的项
|
||||
|
||||
getTree();
|
||||
watch(selectedKeys, (n) => {
|
||||
emits('change', n[0]);
|
||||
});
|
||||
|
||||
function getTree() {
|
||||
loading.value = true;
|
||||
const params = {
|
||||
|
@ -119,7 +115,7 @@ function getTree() {
|
|||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
}
|
||||
const search = debounce(() => {
|
||||
const key = searchValue.value;
|
||||
const treeArray = new Map();
|
||||
|
@ -167,14 +163,30 @@ function delDepartment(id: string) {
|
|||
getTree();
|
||||
});
|
||||
}
|
||||
|
||||
function refresh(id: string) {
|
||||
// @ts-ignore
|
||||
window?.onSaveSuccess && window.onSaveSuccess(id);
|
||||
window.close();
|
||||
getTree();
|
||||
}
|
||||
|
||||
// 弹窗
|
||||
const editDialogRef = ref(); // 新增弹窗实例
|
||||
const openDialog = (row: any = {}) => {
|
||||
editDialogRef.value.openDialog(true, row);
|
||||
};
|
||||
|
||||
init();
|
||||
function init() {
|
||||
getTree();
|
||||
watch(selectedKeys, (n) => {
|
||||
emits('change', n[0]);
|
||||
});
|
||||
if (save) {
|
||||
nextTick(() => {
|
||||
openDialog();
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
<AddDeviceOrProductDialog
|
||||
ref="addDialogRef"
|
||||
:query-columns="query.columns"
|
||||
:parent-id="props.parentId"
|
||||
:all-permission="table.permissionList.value"
|
||||
asset-type="device"
|
||||
|
@ -155,14 +156,17 @@ import {
|
|||
getPermission_api,
|
||||
getPermissionDict_api,
|
||||
unBindDeviceOrProduct_api,
|
||||
getDeviceProduct_api,
|
||||
} from '@/api/system/department';
|
||||
import { intersection } from 'lodash-es';
|
||||
|
||||
import { dictType } from '../typing.d.ts';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const emits = defineEmits(['update:bindBool']);
|
||||
const props = defineProps<{
|
||||
parentId: string;
|
||||
bindBool: boolean;
|
||||
}>();
|
||||
const query = {
|
||||
columns: [
|
||||
|
@ -186,6 +190,41 @@ const query = {
|
|||
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: '状态',
|
||||
dataIndex: 'state',
|
||||
|
@ -279,26 +318,30 @@ const table = {
|
|||
const { pageIndex, pageSize, total, data } =
|
||||
resp.result as resultType;
|
||||
const ids = data.map((item) => item.id);
|
||||
getPermission_api('device',ids, parentId).then((perResp: any) => {
|
||||
const permissionObj = {};
|
||||
perResp.result.forEach((item: any) => {
|
||||
permissionObj[item.assetId] = item.grantedPermissions;
|
||||
});
|
||||
data.forEach(
|
||||
(item) => (item.permission = permissionObj[item.id]),
|
||||
);
|
||||
getPermission_api('device', ids, parentId).then(
|
||||
(perResp: any) => {
|
||||
const permissionObj = {};
|
||||
perResp.result.forEach((item: any) => {
|
||||
permissionObj[item.assetId] =
|
||||
item.grantedPermissions;
|
||||
});
|
||||
data.forEach(
|
||||
(item) =>
|
||||
(item.permission = permissionObj[item.id]),
|
||||
);
|
||||
|
||||
resolve({
|
||||
code: 200,
|
||||
result: {
|
||||
data: data,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
total,
|
||||
},
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
resolve({
|
||||
code: 200,
|
||||
result: {
|
||||
data: data,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
total,
|
||||
},
|
||||
status: 200,
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}),
|
||||
// 整理参数并获取数据
|
||||
|
@ -393,6 +436,10 @@ const addDialogRef = ref();
|
|||
const editDialogRef = ref();
|
||||
|
||||
table.init();
|
||||
nextTick(() => {
|
||||
props.bindBool && table.clickAdd();
|
||||
emits('update:bindBool', false);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -7,13 +7,19 @@
|
|||
<div class="right">
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="product" tab="产品">
|
||||
<Product :parentId="departmentId" />
|
||||
<Product
|
||||
:parentId="departmentId"
|
||||
@open-device-bind="openDeviceBind"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="device" tab="设备">
|
||||
<Device :parentId="departmentId" />
|
||||
<Device
|
||||
:parentId="departmentId"
|
||||
v-model:bindBool="bindBool"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="user" tab="用户">
|
||||
<User />
|
||||
<User :parentId="departmentId" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
|
@ -30,6 +36,12 @@ import User from './user/index.vue';
|
|||
const activeKey = ref<'product' | 'device' | 'user'>('product');
|
||||
|
||||
const departmentId = ref<string>('');
|
||||
|
||||
const bindBool = ref<boolean>(false);
|
||||
const openDeviceBind = () => {
|
||||
bindBool.value = true;
|
||||
activeKey.value = 'device';
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -125,10 +125,11 @@
|
|||
<div class="dialogs">
|
||||
<AddDeviceOrProductDialog
|
||||
ref="addDialogRef"
|
||||
:query-columns="query.columns"
|
||||
:parent-id="props.parentId"
|
||||
:all-permission="table.permissionList.value"
|
||||
asset-type="product"
|
||||
@confirm="table.refresh"
|
||||
@confirm="table.addConfirm"
|
||||
/>
|
||||
<EditPermissionDialog
|
||||
ref="editDialogRef"
|
||||
|
@ -137,6 +138,7 @@
|
|||
asset-type="product"
|
||||
@confirm="table.refresh"
|
||||
/>
|
||||
<NextDialog ref="nextDialogRef" @confirm="emits('openDeviceBind')" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -149,6 +151,7 @@ import {
|
|||
} from '@ant-design/icons-vue';
|
||||
import AddDeviceOrProductDialog from '../components/AddDeviceOrProductDialog.vue';
|
||||
import EditPermissionDialog from '../components/EditPermissionDialog.vue';
|
||||
import NextDialog from '../components/NextDialog.vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import {
|
||||
getDeviceOrProductList_api,
|
||||
|
@ -161,6 +164,7 @@ import { intersection } from 'lodash-es';
|
|||
import { dictType } from '../typing.d.ts';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const emits = defineEmits(['openDeviceBind'])
|
||||
const props = defineProps<{
|
||||
parentId: string;
|
||||
}>();
|
||||
|
@ -196,16 +200,12 @@ const query = {
|
|||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: '在线',
|
||||
value: 'online',
|
||||
},
|
||||
{
|
||||
label: '离线',
|
||||
value: 'offline',
|
||||
label: '正常',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '禁用',
|
||||
value: 'notActive',
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -279,26 +279,43 @@ const table = {
|
|||
const { pageIndex, pageSize, total, data } =
|
||||
resp.result as resultType;
|
||||
const ids = data.map((item) => item.id);
|
||||
getPermission_api('product', ids, parentId).then((perResp: any) => {
|
||||
const permissionObj = {};
|
||||
perResp.result.forEach((item: any) => {
|
||||
permissionObj[item.assetId] = item.grantedPermissions;
|
||||
});
|
||||
data.forEach(
|
||||
(item) => (item.permission = permissionObj[item.id]),
|
||||
);
|
||||
getPermission_api('product', ids, parentId).then(
|
||||
(perResp: any) => {
|
||||
const permissionObj = {};
|
||||
perResp.result.forEach((item: any) => {
|
||||
permissionObj[item.assetId] =
|
||||
item.grantedPermissions;
|
||||
});
|
||||
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({
|
||||
code: 200,
|
||||
result: {
|
||||
data: data,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
total,
|
||||
},
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
resolve({
|
||||
code: 200,
|
||||
result: {
|
||||
data: data,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
total,
|
||||
},
|
||||
status: 200,
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}),
|
||||
// 整理参数并获取数据
|
||||
|
@ -350,6 +367,7 @@ const table = {
|
|||
},
|
||||
clickEdit: (row?: any) => {
|
||||
const ids = row ? [row.id] : [...table._selectedRowKeys.value];
|
||||
|
||||
if (row || table.selectedRows.length === 1) {
|
||||
const permissionList =
|
||||
row?.permission || table.selectedRows[0].permission;
|
||||
|
@ -387,11 +405,15 @@ const table = {
|
|||
tableRef.value.reload();
|
||||
});
|
||||
},
|
||||
addConfirm: ()=>{
|
||||
table.refresh()
|
||||
nextDialogRef.value && nextDialogRef.value.openDialog()
|
||||
}
|
||||
};
|
||||
|
||||
const addDialogRef = ref();
|
||||
const editDialogRef = ref();
|
||||
|
||||
const nextDialogRef = ref();
|
||||
table.init();
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
import { FormInstance, message } from 'ant-design-vue';
|
||||
import { saveRole_api } from '@/api/system/role';
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
// 弹窗相关
|
||||
const dialog = reactive({
|
||||
visible: false,
|
||||
|
@ -56,12 +57,17 @@ const dialog = reactive({
|
|||
if (resp.status === 200) {
|
||||
message.success('操作成功');
|
||||
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;
|
||||
form.data = { name: '', description: '', ...defaultForm };
|
||||
},
|
||||
|
@ -76,12 +82,10 @@ const form = reactive({
|
|||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
// 将打开弹窗的操作暴露给父组件
|
||||
defineExpose({
|
||||
openDialog: dialog.changeVisible
|
||||
})
|
||||
openDialog: dialog.changeVisible,
|
||||
});
|
||||
</script>
|
||||
|
||||
<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