feat: 规则编排

This commit is contained in:
leiqiaochu 2023-02-15 18:52:47 +08:00
parent 648ca2d7ff
commit b493288b2e
11 changed files with 887 additions and 463 deletions

View File

@ -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}`)

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -72,7 +72,7 @@ const props = defineProps({
} }
.content-right { .content-right {
width: 0; width: 0;
height: 100%; height: 123px;
display: flex; display: flex;
flex-grow: .7; flex-grow: .7;
align-items: flex-end; align-items: flex-end;

View File

@ -24,10 +24,12 @@
:footer="onlineFooter" :footer="onlineFooter"
:value="onlineToday" :value="onlineToday"
> >
<BarChart <!-- <BarChart
:chartXData="barChartXData" :chartXData="barChartXData"
:chartYData="barChartYData" :chartYData="barChartYData"
></BarChart> </TopCard ></BarChart> -->
<Charts :options="onlineOptions"></Charts>
</TopCard
></a-col> ></a-col>
<a-col :span="6" <a-col :span="6"
><TopCard ><TopCard
@ -35,10 +37,7 @@
:footer="messageFooter" :footer="messageFooter"
:value="dayMessage" :value="dayMessage"
> >
<LineChart <Charts :options="TodayDevOptions"></Charts> </TopCard
:chartXData="lineChartXData"
:chartYData="lineChartYData"
></LineChart> </TopCard
></a-col> ></a-col>
</a-row> </a-row>
<a-row :span="24"> <a-row :span="24">
@ -55,7 +54,7 @@
</template> </template>
</Guide> </Guide>
<div class="message-chart"> <div class="message-chart">
<MessageChart :x="messageChartXData" :y="messageChartYData" :maxY="messageMaxChartYData"></MessageChart> <Charts :options="devMegOptions"></Charts>
</div> </div>
</div> </div>
</a-col> </a-col>
@ -74,11 +73,9 @@
</page-container> </page-container>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import BarChart from './components/BarChart.vue';
import LineChart from './components/LineChart.vue';
import TimeSelect from './components/TimeSelect.vue'; import TimeSelect from './components/TimeSelect.vue';
import Charts from './components/Charts.vue'
import Guide from './components/Guide.vue'; import Guide from './components/Guide.vue';
import MessageChart from './components/messageChart.vue';
import { import {
productCount, productCount,
deviceCount, deviceCount,
@ -130,13 +127,12 @@ let messageFooter = ref<Footer[]>([
value: 0, value: 0,
}, },
]); ]);
let lineChartYData = ref<any[]>([]);
let lineChartXData = ref<any[]>([]);
let barChartXData = ref<any[]>([]);
let barChartYData = ref<any[]>([]);
let messageChartXData = ref<any[]>([]); let messageChartXData = ref<any[]>([]);
let messageChartYData = ref<any[]>([]); let messageChartYData = ref<any[]>([]);
let messageMaxChartYData = ref<number>(); let messageMaxChartYData = ref<number>();
let onlineOptions = ref<any>({});
let TodayDevOptions = ref<any>({});
let devMegOptions = ref<any>({});
const quickBtnList = [ const quickBtnList = [
{ label: '昨日', value: 'yesterday' }, { label: '昨日', value: 'yesterday' },
{ label: '近一周', value: 'week' }, { label: '近一周', value: 'week' },
@ -215,13 +211,165 @@ const getOnline = () => {
const x = res.result const x = res.result
.map((item: any) => item.data.timeString) .map((item: any) => item.data.timeString)
.reverse(); .reverse();
barChartXData.value = x;
const y = res.result.map((item: any) => item.data.value); const y = res.result.map((item: any) => item.data.value);
barChartYData.value = y; const onlineYdata = y;
onlineYdata.reverse()
setOnlineChartOpition(x,onlineYdata);
deviceFooter.value[0].value = y?.[1]; deviceFooter.value[0].value = y?.[1];
} }
}); });
}; };
const setOnlineChartOpition = (x:Array<any>,y:Array<number>):void=>{
onlineOptions.value = {
xAxis: {
type: 'category',
data: x,
show: false,
},
yAxis: {
type: 'value',
show: false,
},
grid: {
top: '5%',
bottom: 0,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
series: [
{
name: '在线数',
data: y,
type: 'bar',
showBackground: true,
itemStyle: {
color: '#D3ADF7',
},
},
],
};
}
const setTodayDevChartOption = (x:Array<any>,y:Array<number>):void =>{
TodayDevOptions = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
xAxis: {
type: 'category',
boundaryGap: false,
show: false,
data:x
},
yAxis: {
type: 'value',
show: false,
},
grid: {
top: '2%',
bottom: 0,
},
series: [
{
name: '消息量',
data: y,
type: 'line',
smooth: true, // 线
symbolSize: 0, //
color: '#F29B55',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#FBBB87', // 100%
},
{
offset: 1,
color: '#FFFFFF', // 0%
},
],
global: false, // false
},
},
},
],
};
}
const setDevMesChartOption = (x:Array<any>,y:Array<number>,maxY:number):void =>{
devMegOptions.value = {
xAxis: {
type: 'category',
boundaryGap: false,
data: x,
},
yAxis: {
type: 'value',
},
tooltip: {
trigger: 'axis',
formatter: '{b0}<br />{a0}: {c0}',
// formatter: '{b0}<br />{a0}: {c0}<br />{a1}: {c1}%'
},
grid: {
top: '2%',
bottom: '5%',
left: maxY > 100000 ? '90px' : '50px',
right: '50px',
},
series: [
{
name: '消息量',
data: y,
type: 'bar',
// type: 'line',
// smooth: true,
color: '#597EF7',
barWidth: '30%',
// areaStyle: {
// color: {
// type: 'linear',
// x: 0,
// y: 0,
// x2: 0,
// y2: 1,
// colorStops: [
// {
// offset: 0,
// color: '#685DEB', // 100%
// },
// {
// offset: 1,
// color: '#FFFFFF', // 0%
// },
// ],
// global: false, // false
// },
// },
},
{
name: '占比',
data: y,
// data: percentageY,
type: 'line',
smooth: true,
symbolSize: 0, //
color: '#96ECE3',
},
],
}
}
getOnline(); getOnline();
// //
const getDevice = () => { const getDevice = () => {
@ -279,8 +427,7 @@ const getDevice = () => {
); );
const x = today.map((item: any) => item.data.timeString).reverse(); const x = today.map((item: any) => item.data.timeString).reverse();
const y = today.map((item: any) => item.data.value).reverse(); const y = today.map((item: any) => item.data.value).reverse();
lineChartXData.value = x; setTodayDevChartOption(x,y);
lineChartYData.value = y;
} }
}); });
}; };
@ -322,15 +469,16 @@ const getEcharts = (data: any) => {
}, },
]).then((res:any) => { ]).then((res:any) => {
if (res.status === 200) { if (res.status === 200) {
messageChartXData.value = res.result const x = res.result
.map((item: any) => .map((item: any) =>
_time === '1h' _time === '1h'
? `${item.data.timeString}` ? `${item.data.timeString}`
: item.data.timeString, : item.data.timeString,
) )
.reverse(); .reverse();
messageChartYData.value = res.result.map((item: any) => item.data.value).reverse(); const y = res.result.map((item: any) => item.data.value).reverse();
messageMaxChartYData.value = Math.max.apply(null, messageChartYData.value.length ? messageChartYData.value : [0]); const maxY = Math.max.apply(null, messageChartYData.value.length ? messageChartYData.value : [0]);
setDevMesChartOption(x,y,maxY);
} }
}); });
}; };

View File

@ -1,163 +1,170 @@
<template> <template>
<a-card class="device-product"> <page-container>
<Search <a-card class="device-product">
:columns="query.columns" <Search
target="product-manage" :columns="query.columns"
@search="handleSearch" target="product-manage"
/> @search="handleSearch"
<JTable />
:columns="columns" <JTable
:request="queryProductList" :columns="columns"
ref="tableRef" :request="queryProductList"
:defaultParams="{ ref="tableRef"
sorts: [{ name: 'createTime', order: 'desc' }], :defaultParams="{
}" sorts: [{ name: 'createTime', order: 'desc' }],
:params="params" }"
> :params="params"
<template #headerTitle> >
<a-space> <template #headerTitle>
<a-button type="primary" @click="add" <a-space>
><plus-outlined />新增</a-button <a-button type="primary" @click="add"
> ><plus-outlined />新增</a-button
<a-upload
name="file"
accept=".json"
:showUploadList="false"
:before-upload="beforeUpload"
>
<a-button>导入</a-button>
</a-upload>
</a-space>
</template>
<template #deviceType="slotProps">
<div>{{ slotProps.deviceType.text }}</div>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
@click="handleClick"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:active="_selectedRowKeys.includes(slotProps.id)"
:status="slotProps.state"
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
:statusNames="{
1: 'success',
0: 'error',
}"
>
<template #img>
<slot name="img">
<img :src="getImage('/device-product.png')" />
</slot>
</template>
<template #content>
<h3
@click.stop="handleView(slotProps.id)"
style="font-weight: 600"
> >
{{ slotProps.name }} <a-upload
</h3> name="file"
<a-row> accept=".json"
<a-col :span="12"> :showUploadList="false"
<div class="card-item-content-text"> :before-upload="beforeUpload"
设备类型 >
</div> <a-button>导入</a-button>
<div>直连设备</div> </a-upload>
</a-col> </a-space>
</a-row> </template>
</template> <template #deviceType="slotProps">
<template #actions="item"> <div>{{ slotProps.deviceType.text }}</div>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
@click="handleClick"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:active="_selectedRowKeys.includes(slotProps.id)"
:status="slotProps.state"
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
:statusNames="{
1: 'success',
0: 'error',
}"
>
<template #img>
<slot name="img">
<img :src="getImage('/device-product.png')" />
</slot>
</template>
<template #content>
<h3
@click.stop="handleView(slotProps.id)"
style="font-weight: 600"
>
{{ slotProps.name }}
</h3>
<a-row>
<a-col :span="12">
<div class="card-item-content-text">
设备类型
</div>
<div>直连设备</div>
</a-col>
</a-row>
</template>
<template #actions="item">
<a-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
>
<a-popconfirm
v-if="item.popConfirm"
v-bind="item.popConfirm"
:disabled="item.disabled"
okText="确定"
cancelText="取消"
>
<a-button :disabled="item.disabled">
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</a-button>
</a-popconfirm>
<template v-else>
<a-button
:disabled="item.disabled"
@click="item.onClick"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</a-button>
</template>
</a-tooltip>
</template>
</CardBox>
</template>
<template #state="slotProps">
<a-badge
:text="slotProps.state === 1 ? '正常' : '禁用'"
:status="statusMap.get(slotProps.state)"
/>
</template>
<template #id="slotProps">
<a>{{ slotProps.id }}</a>
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip <a-tooltip
v-bind="item.tooltip" v-for="i in getActions(slotProps)"
:title="item.disabled && item.tooltip.title" :key="i.key"
v-bind="i.tooltip"
> >
<a-popconfirm <a-popconfirm
v-if="item.popConfirm" v-if="i.popConfirm"
v-bind="item.popConfirm" v-bind="i.popConfirm"
:disabled="item.disabled"
okText="确定" okText="确定"
cancelText="取消" cancelText="取消"
> >
<a-button :disabled="item.disabled">
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</a-button>
</a-popconfirm>
<template v-else>
<a-button <a-button
:disabled="item.disabled" :disabled="i.disabled"
@click="item.onClick" style="padding: 0"
> type="link"
<AIcon ><AIcon :type="i.icon"
type="DeleteOutlined" /></a-button>
v-if="item.key === 'delete'" </a-popconfirm>
/> <a-button
<template v-else> style="padding: 0"
<AIcon :type="item.icon" /> type="link"
<span>{{ item?.text }}</span> v-else
</template> @click="i.onClick && i.onClick(slotProps)"
</a-button> >
</template> <a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip> </a-tooltip>
</template> </a-space>
</CardBox> </template>
</template> </JTable>
<template #state="slotProps"> <!-- 新增编辑 -->
<a-badge <Save
:text="slotProps.state === 1 ? '正常' : '禁用'" ref="saveRef"
:status="statusMap.get(slotProps.state)" :isAdd="isAdd"
/> :title="title"
</template> @success="refresh"
<template #id="slotProps"> />
<a>{{ slotProps.id }}</a> </a-card>
</template> </page-container>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
v-for="i in getActions(slotProps)"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm
v-if="i.popConfirm"
v-bind="i.popConfirm"
okText="确定"
cancelText="取消"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-popconfirm>
<a-button
style="padding: 0"
type="link"
v-else
@click="i.onClick && i.onClick(slotProps)"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip>
</a-space>
</template>
</JTable>
<!-- 新增编辑 -->
<Save ref="saveRef" :isAdd="isAdd" :title="title" @success="refresh" />
</a-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -195,7 +202,7 @@ import Save from './Save/index.vue';
const router = useRouter(); const router = useRouter();
const isAdd = ref<number>(0); const isAdd = ref<number>(0);
const title = ref<string>(''); const title = ref<string>('');
const params = <Record<string, any>>{}; const params = ref<Record<string, any>>({});
const statusMap = new Map(); const statusMap = new Map();
statusMap.set(1, 'success'); statusMap.set(1, 'success');
statusMap.set(0, 'error'); statusMap.set(0, 'error');

View File

@ -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>

View File

@ -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>

View File

@ -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
}