Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev
This commit is contained in:
commit
c4e66cc414
|
@ -100,7 +100,7 @@ export const deviceImport = (productId: string, fileUrl: string, autoDeploy: boo
|
|||
* @param type 文件类型
|
||||
* @returns
|
||||
*/
|
||||
export const deviceExport = (productId: string, type: string) => `${BASE_API_PATH}/device-instance${!!productId ? '/' + productId : ''}/export.${type}`
|
||||
export const deviceExport = (productId: string, type: string) => `${BASE_API_PATH}/device-instance${!!productId ? `/${productId}` : ''}/export.${type}`
|
||||
|
||||
/**
|
||||
* 验证设备ID是否重复
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<template #title>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">配置元素</div>
|
||||
<AIcon type="CloseOutlined" @click="visible = false" />
|
||||
<div @click="visible = false"><AIcon type="CloseOutlined" /></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
|
@ -55,7 +55,7 @@ const _value = computed({
|
|||
const visible = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
emit('update:value', { extends: {}, ...props.value })
|
||||
emit('update:value', { expands: {}, ...props.value })
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -9,18 +9,20 @@
|
|||
<template #title>
|
||||
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">枚举项配置</div>
|
||||
<AIcon type="CloseOutlined" @click="handleClose" />
|
||||
<div @click="handleClose"><AIcon type="CloseOutlined" /></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="ant-form-vertical">
|
||||
<j-form-item label="Value" :name="name.concat([index, 'value'])" :rules="[
|
||||
{ required: true, message: '请输入Value' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]">
|
||||
<j-input v-model:value="_value[index].value" size="small"></j-input>
|
||||
</j-form-item>
|
||||
<j-form-item label="Text" :name="name.concat([index, 'text'])" :rules="[
|
||||
{ required: true, message: '请输入Text' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]">
|
||||
<j-input v-model:value="_value[index].text" size="small"></j-input>
|
||||
</j-form-item>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<template #title>
|
||||
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">配置参数</div>
|
||||
<AIcon type="CloseOutlined" @click="handleClose" />
|
||||
<div @click="handleClose"><AIcon type="CloseOutlined" /></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
|
@ -40,8 +40,8 @@
|
|||
</div>
|
||||
</j-popover>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<AIcon type="DeleteOutlined" @click="handleDelete(index)" />
|
||||
<div class="item-right" @click="handleDelete(index)">
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</div>
|
||||
</div>
|
||||
<j-button type="dashed" block @click="handleAdd">
|
||||
|
@ -96,6 +96,7 @@ const handleDelete = (index: number) => {
|
|||
_value.value.splice(index, 1)
|
||||
}
|
||||
const handleClose = () => {
|
||||
console.log(editIndex.value)
|
||||
editIndex.value = -1
|
||||
}
|
||||
const handleAdd = () => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
name="file"
|
||||
:action="FILE_UPLOAD"
|
||||
:headers="{
|
||||
'X-Access-Token': LocalStore.get(TOKEN_KEY)
|
||||
'X-Access-Token': LocalStore.get(TOKEN_KEY),
|
||||
}"
|
||||
accept=".xlsx,.csv"
|
||||
:maxCount="1"
|
||||
|
@ -26,19 +26,23 @@
|
|||
</a-space>
|
||||
<div style="margin-top: 20px" v-if="importLoading">
|
||||
<a-badge v-if="flag" status="processing" text="进行中" />
|
||||
<a-badge v-else status="success" text="已完成" />
|
||||
<span>总数量:{{count}}</span>
|
||||
<p style="color: red">{{errMessage}}</p>
|
||||
<a-badge v-else status="success" text="已完成" />
|
||||
<span>总数量:{{ count }}</span>
|
||||
<p style="color: red">{{ errMessage }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FILE_UPLOAD } from '@/api/comm'
|
||||
import { TOKEN_KEY } from '@/utils/variable';
|
||||
import { FILE_UPLOAD } from '@/api/comm';
|
||||
import { TOKEN_KEY } from '@/utils/variable';
|
||||
import { LocalStore } from '@/utils/comm';
|
||||
import { downloadFile, downloadFileByUrl } from '@/utils/utils';
|
||||
import { deviceImport, deviceTemplateDownload ,templateDownload} from '@/api/device/instance'
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill'
|
||||
import {
|
||||
deviceImport,
|
||||
deviceTemplateDownload,
|
||||
templateDownload,
|
||||
} from '@/api/device/instance';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
type Emits = {
|
||||
|
@ -50,11 +54,11 @@ const props = defineProps({
|
|||
// 组件双向绑定的值
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
product: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
file: {
|
||||
type: Object,
|
||||
|
@ -62,67 +66,62 @@ const props = defineProps({
|
|||
return {
|
||||
fileType: 'xlsx',
|
||||
autoDeploy: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const importLoading = ref<boolean>(false)
|
||||
const flag = ref<boolean>(false)
|
||||
const count = ref<number>(0)
|
||||
const errMessage = ref<string>('')
|
||||
const importLoading = ref<boolean>(false);
|
||||
const flag = ref<boolean>(false);
|
||||
const count = ref<number>(0);
|
||||
const errMessage = ref<string>('');
|
||||
|
||||
const downFile =async (type: string) => {
|
||||
const downFile = async (type: string) => {
|
||||
// downloadFile(deviceTemplateDownload(props.product, type));
|
||||
const res:any =await templateDownload(props.product, type)
|
||||
if(res){
|
||||
const res: any = await templateDownload(props.product, type);
|
||||
if (res) {
|
||||
const blob = new Blob([res], { type: type });
|
||||
const url = URL.createObjectURL(blob);
|
||||
console.log(url);
|
||||
downloadFileByUrl(
|
||||
url,
|
||||
`设备导入模版`,
|
||||
type,
|
||||
);
|
||||
const url = URL.createObjectURL(blob);
|
||||
downloadFileByUrl(url, `设备导入模版`, type);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const submitData = async (fileUrl: string) => {
|
||||
if (!!fileUrl) {
|
||||
count.value = 0
|
||||
errMessage.value = ''
|
||||
flag.value = true
|
||||
const autoDeploy = !!props?.file?.autoDeploy || false;
|
||||
importLoading.value = true
|
||||
let dt = 0;
|
||||
const source = new EventSourcePolyfill(deviceImport(props.product, fileUrl, autoDeploy));
|
||||
source.onmessage = (e: any) => {
|
||||
const res = JSON.parse(e.data);
|
||||
if (res.success) {
|
||||
const temp = res.result.total;
|
||||
dt += temp;
|
||||
count.value = dt
|
||||
} else {
|
||||
errMessage.value = res.message || '失败'
|
||||
}
|
||||
};
|
||||
source.onerror = (e: { status: number; }) => {
|
||||
if (e.status === 403) errMessage.value = '暂无权限,请联系管理员'
|
||||
flag.value = false
|
||||
source.close();
|
||||
};
|
||||
source.onopen = () => {};
|
||||
count.value = 0;
|
||||
errMessage.value = '';
|
||||
flag.value = true;
|
||||
const autoDeploy = !!props?.file?.autoDeploy || false;
|
||||
importLoading.value = true;
|
||||
let dt = 0;
|
||||
const source = new EventSourcePolyfill(
|
||||
deviceImport(props.product, fileUrl, autoDeploy),
|
||||
);
|
||||
source.onmessage = (e: any) => {
|
||||
const res = JSON.parse(e.data);
|
||||
if (res.success) {
|
||||
const temp = res.result.total;
|
||||
dt += temp;
|
||||
count.value = dt;
|
||||
} else {
|
||||
errMessage.value = res.message || '失败';
|
||||
}
|
||||
};
|
||||
source.onerror = (e: { status: number }) => {
|
||||
if (e.status === 403) errMessage.value = '暂无权限,请联系管理员';
|
||||
flag.value = false;
|
||||
source.close();
|
||||
};
|
||||
source.onopen = () => {};
|
||||
} else {
|
||||
message.error('请先上传文件')
|
||||
message.error('请先上传文件');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const uploadChange = async (info: Record<string, any>) => {
|
||||
if (info.file.status === 'done') {
|
||||
const resp: any = info.file.response || { result: '' };
|
||||
await submitData(resp?.result || '');
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
|
@ -45,4 +45,5 @@ export const SystemConst = {
|
|||
GET_METADATA: 'get_metadata',
|
||||
REFRESH_DEVICE: 'refresh_device',
|
||||
VERSION_CODE: 'version_code',
|
||||
AMAP_KEY : 'amap_key',
|
||||
}
|
||||
|
|
|
@ -1,19 +1,46 @@
|
|||
<template>
|
||||
<div style="width: 100%; height: 400px">
|
||||
<el-amap
|
||||
>
|
||||
</el-amap>
|
||||
<AmapComponent>
|
||||
<el-amap-label-marker
|
||||
v-for="i in point"
|
||||
:key="i"
|
||||
:position="i.geometry.coordinates"
|
||||
:text="{
|
||||
content: i.properties.deviceName,
|
||||
direction: 'right',
|
||||
style: {
|
||||
fontSize: 15,
|
||||
fillColor: '#fff',
|
||||
strokeColor: 'rgba(255,0,0,0.5)',
|
||||
strokeWidth: 2,
|
||||
padding: [3, 10],
|
||||
backgroundColor: 'yellow',
|
||||
borderColor: '#ccc',
|
||||
borderWidth: 3,
|
||||
},
|
||||
}"
|
||||
:icon="{
|
||||
image: 'https://a.amap.com/jsapi_demos/static/images/poi-marker.png',
|
||||
anchor: 'bottom-center',
|
||||
size: [25, 34],
|
||||
clipOrigin: [459, 92],
|
||||
clipSize: [50, 68],
|
||||
}"
|
||||
>123</el-amap-label-marker
|
||||
>
|
||||
</AmapComponent>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { initAMapApiLoader } from '@vuemap/vue-amap';
|
||||
import '@vuemap/vue-amap/dist/style.css';
|
||||
initAMapApiLoader({
|
||||
// key: '95fa72137f4263f8e64ae01f766ad09c',
|
||||
key: 'a0415acfc35af15f10221bfa5a6850b4',
|
||||
securityJsCode: 'cae6108ec3dd222f946d1a7237c78be0',
|
||||
});
|
||||
import AmapComponent from '@/components/AMapComponent/index.vue';
|
||||
import { getGo } from '@/api/device/dashboard';
|
||||
let point = ref();
|
||||
const getMapData = async () => {
|
||||
const res = await getGo({});
|
||||
point.value = res.result?.features;
|
||||
};
|
||||
getMapData();
|
||||
</script>
|
||||
<style scoped>
|
||||
</style>
|
|
@ -28,8 +28,7 @@
|
|||
:chartXData="barChartXData"
|
||||
:chartYData="barChartYData"
|
||||
></BarChart> -->
|
||||
<Charts :options="onlineOptions"></Charts>
|
||||
</TopCard
|
||||
<Charts :options="onlineOptions"></Charts> </TopCard
|
||||
></a-col>
|
||||
<a-col :span="6"
|
||||
><TopCard
|
||||
|
@ -54,7 +53,7 @@
|
|||
</template>
|
||||
</Guide>
|
||||
<div class="message-chart">
|
||||
<Charts :options="devMegOptions"></Charts>
|
||||
<Charts :options="devMegOptions"></Charts>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
|
@ -74,7 +73,7 @@
|
|||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TimeSelect from './components/TimeSelect.vue';
|
||||
import Charts from './components/Charts.vue'
|
||||
import Charts from './components/Charts.vue';
|
||||
import Guide from './components/Guide.vue';
|
||||
import {
|
||||
productCount,
|
||||
|
@ -86,7 +85,8 @@ import encodeQuery from '@/utils/encodeQuery';
|
|||
import { getImage } from '@/utils/comm';
|
||||
import type { Footer } from '@/views/device/DashBoard/typings';
|
||||
import TopCard from '@/views/device/DashBoard/components/TopCard.vue';
|
||||
import Amap from './components/Amap.vue'
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import Amap from './components/Amap.vue';
|
||||
let productTotal = ref(0);
|
||||
let productFooter = ref<Footer[]>([
|
||||
{
|
||||
|
@ -133,6 +133,7 @@ let messageMaxChartYData = ref<number>();
|
|||
let onlineOptions = ref<any>({});
|
||||
let TodayDevOptions = ref<any>({});
|
||||
let devMegOptions = ref<any>({});
|
||||
const menuStore = useMenuStore();
|
||||
const quickBtnList = [
|
||||
{ label: '昨日', value: 'yesterday' },
|
||||
{ label: '近一周', value: 'week' },
|
||||
|
@ -140,54 +141,60 @@ const quickBtnList = [
|
|||
{ label: '近一年', value: 'year' },
|
||||
];
|
||||
const getProductData = () => {
|
||||
productCount().then((res) => {
|
||||
if (res.status == 200) {
|
||||
productTotal.value = res.result;
|
||||
}
|
||||
});
|
||||
productCount({
|
||||
terms: [
|
||||
{
|
||||
column: 'state',
|
||||
value: '1',
|
||||
},
|
||||
],
|
||||
}).then((res) => {
|
||||
if (res.status == 200) {
|
||||
productFooter.value[0].value = res.result;
|
||||
}
|
||||
});
|
||||
productCount({
|
||||
terms: [
|
||||
{
|
||||
column: 'state',
|
||||
value: '0',
|
||||
},
|
||||
],
|
||||
}).then((res) => {
|
||||
if (res.status == 200) {
|
||||
productFooter.value[1].value = res.result;
|
||||
}
|
||||
});
|
||||
if (menuStore.hasMenu('device/Product')) {
|
||||
productCount().then((res) => {
|
||||
if (res.status == 200) {
|
||||
productTotal.value = res.result;
|
||||
}
|
||||
});
|
||||
productCount({
|
||||
terms: [
|
||||
{
|
||||
column: 'state',
|
||||
value: '1',
|
||||
},
|
||||
],
|
||||
}).then((res) => {
|
||||
if (res.status == 200) {
|
||||
productFooter.value[0].value = res.result;
|
||||
}
|
||||
});
|
||||
productCount({
|
||||
terms: [
|
||||
{
|
||||
column: 'state',
|
||||
value: '0',
|
||||
},
|
||||
],
|
||||
}).then((res) => {
|
||||
if (res.status == 200) {
|
||||
productFooter.value[1].value = res.result;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
getProductData();
|
||||
const getDeviceData = () => {
|
||||
deviceCount().then((res) => {
|
||||
if (res.status == 200) {
|
||||
deviceTotal.value = res.result;
|
||||
}
|
||||
});
|
||||
deviceCount(encodeQuery({ terms: { state: 'online' } })).then((res) => {
|
||||
if (res.status == 200) {
|
||||
deviceFooter.value[0].value = res.result;
|
||||
deviceOnline.value = res.result;
|
||||
}
|
||||
});
|
||||
deviceCount(encodeQuery({ terms: { state: 'offline' } })).then((res) => {
|
||||
if (res.status == 200) {
|
||||
deviceFooter.value[1].value = res.result;
|
||||
}
|
||||
});
|
||||
if (menuStore.hasMenu('device/Instance')) {
|
||||
deviceCount().then((res) => {
|
||||
if (res.status == 200) {
|
||||
deviceTotal.value = res.result;
|
||||
}
|
||||
});
|
||||
deviceCount(encodeQuery({ terms: { state: 'online' } })).then((res) => {
|
||||
if (res.status == 200) {
|
||||
deviceFooter.value[0].value = res.result;
|
||||
deviceOnline.value = res.result;
|
||||
}
|
||||
});
|
||||
deviceCount(encodeQuery({ terms: { state: 'offline' } })).then(
|
||||
(res) => {
|
||||
if (res.status == 200) {
|
||||
deviceFooter.value[1].value = res.result;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
getDeviceData();
|
||||
const getOnline = () => {
|
||||
|
@ -213,163 +220,167 @@ const getOnline = () => {
|
|||
.reverse();
|
||||
const y = res.result.map((item: any) => item.data.value);
|
||||
const onlineYdata = y;
|
||||
onlineYdata.reverse()
|
||||
setOnlineChartOpition(x,onlineYdata);
|
||||
onlineYdata.reverse();
|
||||
setOnlineChartOpition(x, onlineYdata);
|
||||
deviceFooter.value[0].value = y?.[1];
|
||||
}
|
||||
});
|
||||
};
|
||||
const setOnlineChartOpition = (x:Array<any>,y:Array<number>):void=>{
|
||||
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,
|
||||
type: 'category',
|
||||
data: x,
|
||||
show: false,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: '{b0}<br />{a0}: {c0}',
|
||||
// formatter: '{b0}<br />{a0}: {c0}<br />{a1}: {c1}%'
|
||||
type: 'value',
|
||||
show: false,
|
||||
},
|
||||
grid: {
|
||||
top: '2%',
|
||||
bottom: '5%',
|
||||
left: maxY > 100000 ? '90px' : '50px',
|
||||
right: '50px',
|
||||
top: '5%',
|
||||
bottom: 0,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
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',
|
||||
},
|
||||
{
|
||||
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 = () => {
|
||||
|
@ -427,7 +438,7 @@ const getDevice = () => {
|
|||
);
|
||||
const x = today.map((item: any) => item.data.timeString).reverse();
|
||||
const y = today.map((item: any) => item.data.value).reverse();
|
||||
setTodayDevChartOption(x,y);
|
||||
setTodayDevChartOption(x, y);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -452,7 +463,7 @@ const getEcharts = (data: any) => {
|
|||
_time = '1M';
|
||||
format = 'yyyy年-M月';
|
||||
}
|
||||
|
||||
|
||||
dashboard([
|
||||
{
|
||||
dashboard: 'device',
|
||||
|
@ -468,7 +479,7 @@ const getEcharts = (data: any) => {
|
|||
to: data.end,
|
||||
},
|
||||
},
|
||||
]).then((res:any) => {
|
||||
]).then((res: any) => {
|
||||
if (res.status === 200) {
|
||||
const x = res.result
|
||||
.map((item: any) =>
|
||||
|
@ -478,23 +489,27 @@ const getEcharts = (data: any) => {
|
|||
)
|
||||
.reverse();
|
||||
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);
|
||||
const maxY = Math.max.apply(
|
||||
null,
|
||||
messageChartYData.value.length ? messageChartYData.value : [0],
|
||||
);
|
||||
setDevMesChartOption(x, y, maxY);
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.message-card,.device-position{
|
||||
.message-card,
|
||||
.device-position {
|
||||
margin-top: 24px;
|
||||
padding: 24px;
|
||||
background-color: white;
|
||||
}
|
||||
.message-chart {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
height: 400px;
|
||||
}
|
||||
.amap-box{
|
||||
.amap-box {
|
||||
height: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -174,7 +174,7 @@ const form = ref();
|
|||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
const { productList } = defineProps(['productList']);
|
||||
const props = defineProps(['productList']);
|
||||
const _emit = defineEmits(['close']);
|
||||
const instanceStore = useInstanceStore();
|
||||
let _metadata = ref();
|
||||
|
@ -279,7 +279,7 @@ const onSave = async () => {
|
|||
});
|
||||
const formData = {
|
||||
...form.value,
|
||||
productName: productList.find(
|
||||
productName: props.productList.find(
|
||||
(item: any) => item.id === form.value?.productId,
|
||||
).name,
|
||||
parentId: instanceStore.current.id,
|
||||
|
@ -319,7 +319,7 @@ const showModal = async () => {
|
|||
if (form.value) {
|
||||
const formData = {
|
||||
...form.value,
|
||||
productName: productList.find(
|
||||
productName: props.productList.find(
|
||||
(item: any) => item.id === form.value?.productId,
|
||||
).name,
|
||||
parentId: instanceStore.current.id,
|
||||
|
|
|
@ -52,6 +52,7 @@ import EdgeMap from '../EdgeMap/index.vue';
|
|||
import { useInstanceStore } from '@/store/instance';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { provide } from 'vue';
|
||||
import { getEdgeMap, removeEdgeMap } from '@/api/device/instance';
|
||||
const instanceStore = useInstanceStore();
|
||||
const { current } = storeToRefs(instanceStore);
|
||||
const props = defineProps(['childData']);
|
||||
|
@ -89,18 +90,71 @@ const selectChange = (e: any) => {
|
|||
};
|
||||
watchEffect(() => {
|
||||
if (props.childData?.id) {
|
||||
current.value.parentId = props.childData.id;
|
||||
form.name = props.childData?.name;
|
||||
form.productId = props.childData?.productId;
|
||||
if (props.childData.deriveMetadata) {
|
||||
const metadata = JSON.parse(
|
||||
props.childData?.deriveMetadata || {},
|
||||
)?.properties?.map((item: any) => ({
|
||||
metadataId: item.id,
|
||||
metadataName: `${item.name}(${item.id})`,
|
||||
metadataType: 'property',
|
||||
name: item.name,
|
||||
}));
|
||||
if (metadata && metadata.length !== 0) {
|
||||
getEdgeMap(current.value.id, {
|
||||
deviceId: props.childData.id,
|
||||
query: {},
|
||||
}).then((res) => {
|
||||
if (res.status === 200) {
|
||||
// console.log(res.result)
|
||||
//合并物模型
|
||||
const array = res.result[0]?.reduce(
|
||||
(x: any, y: any) => {
|
||||
const metadataId = metadata.find(
|
||||
(item: any) =>
|
||||
item.metadataId === y.metadataId,
|
||||
);
|
||||
if (metadataId) {
|
||||
Object.assign(metadataId, y);
|
||||
} else {
|
||||
x.push(y);
|
||||
}
|
||||
return x;
|
||||
},
|
||||
metadata,
|
||||
);
|
||||
//删除物模型
|
||||
const items = array.filter(
|
||||
(item: any) => item.metadataName,
|
||||
);
|
||||
current.value.metadata = items;
|
||||
const delList = array
|
||||
.filter((a: any) => !a.metadataName)
|
||||
.map((b: any) => b.id);
|
||||
//删除后解绑
|
||||
if (delList && delList.length !== 0) {
|
||||
removeEdgeMap(current.value.id, {
|
||||
deviceId: props.childData.id,
|
||||
idList: [...delList],
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
visible.value = true;
|
||||
}
|
||||
});
|
||||
watchEffect(() => {});
|
||||
|
||||
const validate = async () => {
|
||||
return formRef.value.validateFields();
|
||||
return formRef.value.validateFields();
|
||||
};
|
||||
provide('validate',validate);
|
||||
const comeBack = () =>{
|
||||
provide('validate', validate);
|
||||
const comeBack = () => {
|
||||
emit('closeChildSave');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -123,7 +123,7 @@ import { usePermissionStore } from '@/store/permission';
|
|||
import SaveChild from './SaveChild/index.vue';
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
const { detail } = storeToRefs(instanceStore);
|
||||
const { detail } = storeToRefs(instanceStore);
|
||||
const router = useRouter();
|
||||
const childVisible = ref(false);
|
||||
const permissionStore = usePermissionStore();
|
||||
|
|
|
@ -1693,7 +1693,7 @@ const Status = defineComponent({
|
|||
/>
|
||||
)}
|
||||
{
|
||||
bindParentVisible && (
|
||||
bindParentVisible.value && (
|
||||
<BindParentDevice
|
||||
data={device.value}
|
||||
onCancel={() => {
|
||||
|
|
|
@ -25,13 +25,13 @@
|
|||
instanceStore.current.deviceType?.text
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="固件版本">{{
|
||||
instanceStore.current.firmwareInfo?.version
|
||||
instanceStore.current?.firmwareInfo?.version
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="连接协议">{{
|
||||
instanceStore.current?.protocolName
|
||||
instanceStore.current?.transport
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="消息协议">{{
|
||||
instanceStore.current.transport
|
||||
instanceStore.current.protocolName
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="创建时间">{{
|
||||
instanceStore.current.createTime
|
||||
|
|
|
@ -109,7 +109,9 @@ const loading = ref<boolean>(false);
|
|||
const instanceStore = useInstanceStore();
|
||||
const formRef = ref();
|
||||
|
||||
const modelRef = reactive({
|
||||
const modelRef = reactive<{
|
||||
metrics: any[]
|
||||
}>({
|
||||
metrics: [],
|
||||
});
|
||||
|
||||
|
|
|
@ -23,11 +23,12 @@
|
|||
:tab="i.tab"
|
||||
/>
|
||||
</j-tabs>
|
||||
<JEmpty v-else style="margin: 250px 0" />
|
||||
<JEmpty v-else style="margin: 180px 0" />
|
||||
</div>
|
||||
<div class="property-box-right">
|
||||
<Event v-if="type === 'event'" :data="data" />
|
||||
<Property v-else :data="properties" />
|
||||
<Property v-else-if="type === 'property'" :data="properties" />
|
||||
<JEmpty v-else style="margin: 220px 0" />
|
||||
</div>
|
||||
</div>
|
||||
</j-card>
|
||||
|
@ -97,6 +98,13 @@ const onSearch = () => {
|
|||
} else {
|
||||
tabList.value = _.cloneDeep(arr)
|
||||
}
|
||||
const dt = tabList.value?.[0]
|
||||
if (dt) {
|
||||
data.value = dt
|
||||
type.value = dt.type;
|
||||
} else {
|
||||
type.value = ''
|
||||
}
|
||||
};
|
||||
const tabChange = (key: string) => {
|
||||
const dt = tabList.value.find((i) => i.key === key);
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
<div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<AIcon type="ArrowLeftOutlined" @click="onBack" />
|
||||
<div style="margin-left: 20px">{{ instanceStore.current.name }}</div>
|
||||
<div style="margin-left: 20px">
|
||||
{{ instanceStore.current.name }}
|
||||
</div>
|
||||
<j-divider type="vertical" />
|
||||
<j-space>
|
||||
<j-badge
|
||||
|
@ -79,7 +81,7 @@
|
|||
<j-descriptions-item label="所属产品">
|
||||
<PermissionButton
|
||||
type="link"
|
||||
style="margin-top: -5px; padding: 0"
|
||||
style="margin-top: -5px; padding: 0"
|
||||
@click="jumpProduct"
|
||||
hasPermission="device/Product:view"
|
||||
>
|
||||
|
@ -116,8 +118,8 @@ import Function from './Function/index.vue';
|
|||
import Modbus from './Modbus/index.vue';
|
||||
import OPCUA from './OPCUA/index.vue';
|
||||
import EdgeMap from './EdgeMap/index.vue';
|
||||
import Parsing from './Parsing/index.vue'
|
||||
import Log from './Log/index.vue'
|
||||
import Parsing from './Parsing/index.vue';
|
||||
import Log from './Log/index.vue';
|
||||
import { _deploy, _disconnect } from '@/api/device/instance';
|
||||
import { message } from 'jetlinks-ui-components';
|
||||
import { getImage } from '@/utils/comm';
|
||||
|
@ -149,17 +151,13 @@ const list = ref([
|
|||
key: 'Metadata',
|
||||
tab: '物模型',
|
||||
},
|
||||
{
|
||||
key: 'Log',
|
||||
tab: '日志管理',
|
||||
},
|
||||
{
|
||||
key: 'Function',
|
||||
tab: '设备功能',
|
||||
},
|
||||
{
|
||||
key: 'ChildDevice',
|
||||
tab: '子设备',
|
||||
key: 'Log',
|
||||
tab: '日志管理',
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -174,7 +172,7 @@ const tabs = {
|
|||
OPCUA,
|
||||
EdgeMap,
|
||||
Parsing,
|
||||
Log
|
||||
Log,
|
||||
};
|
||||
|
||||
const getStatus = (id: string) => {
|
||||
|
@ -255,6 +253,17 @@ watchEffect(() => {
|
|||
tab: '设备诊断',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.features?.find(
|
||||
(item: any) => item.id === 'transparentCodec',
|
||||
) &&
|
||||
!keys.includes('Parsing')
|
||||
) {
|
||||
list.value.push({
|
||||
key: 'Parsing',
|
||||
tab: '数据解析',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.protocol === 'modbus-tcp' &&
|
||||
!keys.includes('Modbus')
|
||||
|
@ -273,6 +282,13 @@ watchEffect(() => {
|
|||
tab: 'OPC UA',
|
||||
});
|
||||
}
|
||||
if (instanceStore.current.deviceType?.value === 'gateway') {
|
||||
// 产品类型为网关的情况下才显示此模块
|
||||
list.value.push({
|
||||
key: 'ChildDevice',
|
||||
tab: '子设备',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.accessProvider === 'edge-child-device' &&
|
||||
instanceStore.current.parentId &&
|
||||
|
@ -283,15 +299,6 @@ watchEffect(() => {
|
|||
tab: '边缘端映射',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.features?.find((item: any) => item.id === 'transparentCodec') &&
|
||||
!keys.includes('Parsing')
|
||||
) {
|
||||
list.value.push({
|
||||
key: 'Parsing',
|
||||
tab: '数据解析',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
|
|
@ -51,7 +51,6 @@
|
|||
import { queryNoPagingPost } from '@/api/device/product';
|
||||
import { downloadFile } from '@/utils/utils';
|
||||
import encodeQuery from '@/utils/encodeQuery';
|
||||
import { BASE_API_PATH } from '@/utils/variable';
|
||||
import { deviceExport } from '@/api/device/instance';
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
|
|
|
@ -1,92 +1,106 @@
|
|||
<template>
|
||||
<j-modal :maskClosable="false" width="800px" :visible="true" title="当前进度" @ok="handleCancel" @cancel="handleCancel">
|
||||
<j-modal
|
||||
:maskClosable="false"
|
||||
width="800px"
|
||||
:visible="true"
|
||||
title="当前进度"
|
||||
@ok="handleCancel"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div>
|
||||
<j-badge v-if="flag" status="processing" text="进行中" />
|
||||
<j-badge v-else status="success" text="已完成" />
|
||||
<j-badge v-else status="success" text="已完成" />
|
||||
</div>
|
||||
<p>总数量:{{count}}</p>
|
||||
<a style="color: red">{{errMessage}}</a>
|
||||
<p>总数量:{{ count }}</p>
|
||||
<a style="color: red">{{ errMessage }}</a>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill'
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
const props = defineProps({
|
||||
api: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
const eventSource = ref<Record<string, any>>({})
|
||||
const count = ref<number>(0)
|
||||
const flag = ref<boolean>(false)
|
||||
const errMessage = ref<string>('')
|
||||
const isSource = ref<boolean>(false)
|
||||
const id = ref<string>('')
|
||||
const source = ref<Record<string, any>>({})
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
// const eventSource = ref<Record<string, any>>({})
|
||||
const count = ref<number>(0);
|
||||
const flag = ref<boolean>(false);
|
||||
const errMessage = ref<string>('');
|
||||
const isSource = ref<boolean>(false);
|
||||
const id = ref<string>('');
|
||||
const source = ref<Record<string, any>>({});
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('close')
|
||||
}
|
||||
emit('close');
|
||||
emit('save');
|
||||
};
|
||||
|
||||
// const handleOk = () => {
|
||||
// emit('close');
|
||||
// emit('save');
|
||||
// };
|
||||
|
||||
const getData = (api: string) => {
|
||||
let dt = 0
|
||||
const _source = new EventSourcePolyfill(api)
|
||||
source.value = _source
|
||||
let dt = 0;
|
||||
const _source = new EventSourcePolyfill(api);
|
||||
source.value = _source;
|
||||
_source.onmessage = (e: any) => {
|
||||
const res = JSON.parse(e.data);
|
||||
switch (props.type) {
|
||||
case 'active':
|
||||
if (res.success) {
|
||||
dt += res.total;
|
||||
count.value = dt
|
||||
} else {
|
||||
if (res.source) {
|
||||
const msg = `${res.source.name}: ${res.message}`;
|
||||
errMessage.value = msg
|
||||
id.value = res.source.id
|
||||
isSource.value = true
|
||||
} else {
|
||||
errMessage.value = res.message
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'sync':
|
||||
dt += res;
|
||||
count.value = dt
|
||||
break;
|
||||
case 'import':
|
||||
if (res.success) {
|
||||
const temp = res.result.total;
|
||||
dt += temp;
|
||||
count.value = dt
|
||||
} else {
|
||||
errMessage.value = res.message
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const res = JSON.parse(e.data);
|
||||
switch (props.type) {
|
||||
case 'active':
|
||||
if (res.success) {
|
||||
dt += res.total;
|
||||
count.value = dt;
|
||||
} else {
|
||||
if (res.source) {
|
||||
const msg = `${res.source.name}: ${res.message}`;
|
||||
errMessage.value = msg;
|
||||
id.value = res.source.id;
|
||||
isSource.value = true;
|
||||
} else {
|
||||
errMessage.value = res.message;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'sync':
|
||||
dt += res;
|
||||
count.value = dt;
|
||||
break;
|
||||
case 'import':
|
||||
if (res.success) {
|
||||
const temp = res.result.total;
|
||||
dt += temp;
|
||||
count.value = dt;
|
||||
} else {
|
||||
errMessage.value = res.message;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
_source.onerror = () => {
|
||||
flag.value = false
|
||||
_source.close();
|
||||
flag.value = false;
|
||||
_source.close();
|
||||
};
|
||||
_source.onopen = () => {};
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.api,
|
||||
watch(
|
||||
() => props.api,
|
||||
(newValue) => {
|
||||
if(newValue) {
|
||||
getData(newValue)
|
||||
if (newValue) {
|
||||
getData(newValue);
|
||||
}
|
||||
},
|
||||
{deep: true, immediate: true}
|
||||
)
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
</script>
|
|
@ -9,12 +9,7 @@
|
|||
:confirmLoading="loading"
|
||||
>
|
||||
<div style="margin-top: 10px">
|
||||
<j-form
|
||||
:layout="'vertical'"
|
||||
ref="formRef"
|
||||
:rules="rules"
|
||||
:model="modelRef"
|
||||
>
|
||||
<j-form :layout="'vertical'" ref="formRef" :model="modelRef">
|
||||
<j-row type="flex">
|
||||
<j-col flex="180px">
|
||||
<j-form-item name="photoUrl">
|
||||
|
@ -22,14 +17,33 @@
|
|||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col flex="auto">
|
||||
<j-form-item name="id">
|
||||
<j-form-item
|
||||
name="id"
|
||||
:rules="[
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_\-]+$/,
|
||||
message: '请输入英文或者数字或者-或者_',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
{
|
||||
validator: vailId,
|
||||
trigger: 'blur',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
ID
|
||||
<j-tooltip title="若不填写,系统将自动生成唯一ID">
|
||||
<j-tooltip
|
||||
title="若不填写,系统将自动生成唯一ID"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px;" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
|
@ -39,7 +53,20 @@
|
|||
:disabled="!!data?.id"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item label="名称" name="name">
|
||||
<j-form-item
|
||||
label="名称"
|
||||
name="name"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-input
|
||||
v-model:value="modelRef.name"
|
||||
placeholder="请输入名称"
|
||||
|
@ -47,13 +74,23 @@
|
|||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-form-item name="productId">
|
||||
<j-form-item
|
||||
name="productId"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所属产品',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span>所属产品
|
||||
<span
|
||||
>所属产品
|
||||
<j-tooltip title="只能选择“正常”状态的产品">
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
|
@ -68,10 +105,20 @@
|
|||
v-for="item in productList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
>{{item.name}}</j-select-option>
|
||||
>{{ item.name }}</j-select-option
|
||||
>
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
<j-form-item label="说明" name="describe">
|
||||
<j-form-item
|
||||
label="说明"
|
||||
name="describe"
|
||||
:rules="[
|
||||
{
|
||||
max: 200,
|
||||
message: '最多输入200个字符'
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-textarea
|
||||
v-model:value="modelRef.describe"
|
||||
placeholder="请输入说明"
|
||||
|
@ -123,45 +170,6 @@ const vailId = async (_: Record<string, any>, value: string) => {
|
|||
}
|
||||
};
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
],
|
||||
photoUrl: [
|
||||
{
|
||||
required: true,
|
||||
message: '请上传图标',
|
||||
},
|
||||
],
|
||||
productId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所属产品',
|
||||
},
|
||||
],
|
||||
id: [
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
{
|
||||
pattern: /^[j-zA-Z0-9_\-]+$/,
|
||||
message: '请输入英文或者数字或者-或者_',
|
||||
},
|
||||
{
|
||||
validator: vailId,
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(newValue) => {
|
||||
|
@ -199,13 +207,13 @@ const handleSave = () => {
|
|||
.validate()
|
||||
.then(async (_data: any) => {
|
||||
loading.value = true;
|
||||
const obj = {...toRaw(modelRef), ..._data}
|
||||
if(!obj.id){
|
||||
delete obj.id
|
||||
const obj = { ..._data };
|
||||
if (!obj.id) {
|
||||
delete obj.id;
|
||||
}
|
||||
const resp = await update(obj).finally(() => {
|
||||
loading.value = false;
|
||||
})
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
emit('save');
|
||||
|
|
|
@ -252,6 +252,7 @@
|
|||
@close="operationVisible = false"
|
||||
:api="api"
|
||||
:type="type"
|
||||
@save="onRefresh"
|
||||
/>
|
||||
<Save
|
||||
v-if="visible"
|
||||
|
@ -314,6 +315,7 @@ const columns = [
|
|||
key: 'id',
|
||||
search: {
|
||||
type: 'string',
|
||||
defaultTermType: 'eq'
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -322,6 +324,7 @@ const columns = [
|
|||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
first: true
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -389,6 +392,7 @@ const columns = [
|
|||
hideInTable: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
rename: 'productId$product-info',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
getProviders().then((resp: any) => {
|
||||
|
|
|
@ -31,11 +31,11 @@
|
|||
</j-radio-group>
|
||||
</j-form-item>
|
||||
<j-form-item label="输入参数" name="inputs" :rules="[
|
||||
{ required: true, message: '请输入输入参数' },
|
||||
{ required: true, validator: (_rule: Rule, val: Record<any, any>[]) => validateJson(_rule, val, '输入参数') },
|
||||
]">
|
||||
<JsonParam v-model:value="value.inputs" :name="['inputs']"></JsonParam>
|
||||
</j-form-item>
|
||||
<value-type-form :name="['output']" v-model:value="value.output" key="function" title="输出参数"></value-type-form>
|
||||
<value-type-form :name="['output']" v-model:value="value.output" key="function" title="输出参数" :required="false"></value-type-form>
|
||||
</template>
|
||||
<template v-if="modelType === 'events'">
|
||||
<j-form-item label="级别" :name="['expands', 'level']" :rules="[
|
||||
|
@ -43,12 +43,12 @@
|
|||
]">
|
||||
<j-select v-model:value="value.expands.level" :options="EventLevel" size="small"></j-select>
|
||||
</j-form-item>
|
||||
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="function" title="输出参数"></value-type-form>
|
||||
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="function" title="输出参数" only-object></value-type-form>
|
||||
</template>
|
||||
<template v-if="modelType === 'tags'">
|
||||
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="property" title="数据类型"></value-type-form>
|
||||
<j-form-item label="读写类型" :name="['expands', 'type']" :rules="[
|
||||
{ required: true, message: '请选择读写类型' },
|
||||
<j-form-item label="标签类型" :name="['expands', 'type']" :rules="[
|
||||
{ required: true, message: '请选择标签类型' },
|
||||
]">
|
||||
<j-select v-model:value="value.expands.type" :options="ExpandsTypeList" mode="multiple" size="small"></j-select>
|
||||
</j-form-item>
|
||||
|
@ -68,6 +68,8 @@ import { getMetadataConfig } from '@/api/device/product'
|
|||
import JsonParam from '@/components/Metadata/JsonParam/index.vue'
|
||||
import { EventLevel, ExpandsTypeList } from '@/views/device/data';
|
||||
import { useMetadataStore } from '@/store/metadata';
|
||||
import { validateJson } from './validator';
|
||||
import { Rule } from 'ant-design-vue/es/form';
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
{ required: true, message: '请选择来源' },
|
||||
]">
|
||||
<j-select v-model:value="_value.source" :options="PropertySource" size="small"
|
||||
:disabled="metadataStore.model.action === 'edit'"></j-select>
|
||||
:disabled="metadataStore.model.action === 'edit'" @change="changeSource"></j-select>
|
||||
</j-form-item>
|
||||
<virtual-rule-param v-if="_value.source === 'rule'" v-model:value="_value.virtualRule"
|
||||
:name="name.concat(['virtualRule'])" :id="id" :showWindow="_value.source === 'rule'"></virtual-rule-param>
|
||||
<j-form-item label="读写类型" :name="name.concat(['type'])" :rules="[
|
||||
{ required: true, message: '请选择读写类型' },
|
||||
]">
|
||||
<j-select v-model:value="_value.type" :options="ExpandsTypeList" mode="multiple" size="small"></j-select>
|
||||
<j-select v-model:value="_value.type" :options="ExpandsTypeList" mode="multiple" size="small" :disabled="['manual', 'rule'].includes(_value.source)"></j-select>
|
||||
</j-form-item>
|
||||
<j-form-item label="其他配置" v-if="config.length > 0">
|
||||
<j-form-item v-for="(item, index) in config" :key="index">
|
||||
|
@ -100,5 +100,15 @@ const validateMetrics = (value: Record<any, any>[]) => {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const changeSource = (val: string) => {
|
||||
if (val === 'manual') {
|
||||
_value.value.type = ['write']
|
||||
} else if (val === 'rule') {
|
||||
_value.value.type = ['report']
|
||||
} else {
|
||||
_value.value.type = []
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
|
@ -1,23 +1,23 @@
|
|||
<template>
|
||||
<j-form-item :label="title" :name="name.concat(['type'])" :rules="[
|
||||
metadataStore.model.type !== 'functions' ? { required: true, message: `请选择${title}` } : {},
|
||||
required ? { required: true, message: `请选择${title}` } : {},
|
||||
]">
|
||||
<j-select v-model:value="_value.type"
|
||||
:options="metadataStore.model.type === 'events' ? eventDataTypeList : _dataTypeList" size="small"
|
||||
:options="onlyObject ? eventDataTypeList : _dataTypeList" size="small"
|
||||
@change="changeType"></j-select>
|
||||
</j-form-item>
|
||||
<j-form-item label="单位" :name="name.concat(['unit'])" v-if="['int', 'float', 'long', 'double'].includes(_value.type)">
|
||||
<InputSelect v-model:value="_value.unit" :options="unit.unitOptions" size="small"></InputSelect>
|
||||
</j-form-item>
|
||||
<j-form-item label="精度" :name="name.concat(['scale'])" v-if="['float', 'double'].includes(_value.type)">
|
||||
<j-input-number v-model:value="_value.scale" size="small" :min="0" :max="2147483647" :precision="0" :default-value="2"
|
||||
<j-input-number v-model:value="_value.scale" size="small" :min="0" :max="2147483647" :precision="0"
|
||||
style="width: 100%"></j-input-number>
|
||||
</j-form-item>
|
||||
<j-form-item label="布尔值" name="booleanConfig" v-if="['boolean'].includes(_value.type)">
|
||||
<BooleanParam :name="name" v-model:value="_value"></BooleanParam>
|
||||
</j-form-item>
|
||||
<j-form-item label="枚举项" :name="name.concat(['elements'])" v-if="['enum'].includes(_value.type)" :rules="[
|
||||
{ required: true, validator: validateEnum, message: '请配置枚举项' }
|
||||
{ required: true, validator: validateEnum }
|
||||
]">
|
||||
<EnumParam v-model:value="_value.elements" :name="name.concat(['elements'])"></EnumParam>
|
||||
</j-form-item>
|
||||
|
@ -39,7 +39,7 @@
|
|||
<ArrayParam v-model:value="_value.elementType" :name="name.concat(['elementType'])"></ArrayParam>
|
||||
</j-form-item>
|
||||
<j-form-item label="JSON对象" :name="name.concat(['properties'])" v-if="['object'].includes(_value.type)" :rules="[
|
||||
{ validator: validateJson }
|
||||
{ validator: (_rule: Rule, val: Record<any, any>[]) => validateJson(_rule, val, 'JSON对象') }
|
||||
]">
|
||||
<JsonParam v-model:value="_value.properties" :name="name.concat(['properties'])"></JsonParam>
|
||||
</j-form-item>
|
||||
|
@ -62,8 +62,8 @@ import EnumParam from '@/components/Metadata/EnumParam/index.vue'
|
|||
import ArrayParam from '@/components/Metadata/ArrayParam/index.vue'
|
||||
import JsonParam from '@/components/Metadata/JsonParam/index.vue'
|
||||
import { useMetadataStore } from '@/store/metadata';
|
||||
import { validateEnum, validateArray, validateJson } from './validator'
|
||||
import { Rule } from 'ant-design-vue/es/form';
|
||||
import { Form } from 'ant-design-vue/es';
|
||||
|
||||
type ValueType = Record<any, any>;
|
||||
const props = defineProps({
|
||||
|
@ -81,8 +81,16 @@ const props = defineProps({
|
|||
required: true
|
||||
},
|
||||
title: {
|
||||
String,
|
||||
type: String,
|
||||
default: '数据类型'
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
onlyObject: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -108,7 +116,7 @@ watch(_value,
|
|||
{ deep: true, immediate: true })
|
||||
|
||||
onMounted(() => {
|
||||
if (metadataStore.model.type === 'events') {
|
||||
if (props.onlyObject) {
|
||||
_value.value = {
|
||||
type: 'object',
|
||||
expands: {}
|
||||
|
@ -141,63 +149,12 @@ const eventDataTypeList = [
|
|||
]
|
||||
|
||||
const changeType = (val: SelectValue) => {
|
||||
if (['float', 'double'].includes(_value.value.type) && _value.value.scale === undefined) {
|
||||
_value.value.scale = 2
|
||||
}
|
||||
emit('changeType', val as string)
|
||||
}
|
||||
|
||||
const validateEnum = async (_rule: Rule, val: Record<any, any>[]) => {
|
||||
if (val.length === 0) return Promise.reject(new Error('请配置枚举项'));
|
||||
const flag = val.every((item) => {
|
||||
return item.value && item.text;
|
||||
});
|
||||
if (!flag) {
|
||||
return Promise.reject(new Error('请配置枚举项'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const validateArray = async (_rule: Rule, val: Record<any, any>) => {
|
||||
if (!val) return Promise.reject(new Error('请输入元素配置'));
|
||||
await validateValueType(_rule, val)
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const validateJson = async (_rule: Rule, val: Record<any, any>[]) => {
|
||||
if (!val || val.length === 0) {
|
||||
return Promise.reject(new Error('请输入配置参数'));
|
||||
}
|
||||
for (let item of val) {
|
||||
if (!item) return Promise.reject(new Error('请输入配置参数'));
|
||||
await validateValueType(_rule, item)
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const validateValueType = async (_rule: Rule, val: Record<any, any>) => {
|
||||
if (!val) return Promise.reject(new Error('请输入元素配置'));
|
||||
if (!val.id) {
|
||||
return Promise.reject(new Error('请输入标识'))
|
||||
}
|
||||
if (!val.name) {
|
||||
return Promise.reject(new Error('请输入名称'))
|
||||
}
|
||||
if (metadataStore.model.type !== 'functions' && !val.valueType?.type) {
|
||||
return Promise.reject(new Error(`请选择${props.title}`))
|
||||
}
|
||||
if (['enum'].includes(val.valueType.type)) {
|
||||
await validateEnum(_rule, val.valueType.elements)
|
||||
}
|
||||
if (['array'].includes(val.valueType.type)) {
|
||||
await validateArray(_rule, val.valueType.elementType)
|
||||
}
|
||||
if (['object'].includes(val.valueType.type)) {
|
||||
await validateJson(_rule, val.valueType.properties)
|
||||
}
|
||||
if (['file'].includes(val.valueType.type) && !val.valueType.fileType) {
|
||||
return Promise.reject(new Error('请选择文件类型'))
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// const rules = ref({
|
||||
// type: [
|
||||
// metadataStore.model.type !== 'functions' ? { required: true, message: `请选择${props.title}` } : {},
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import { Rule } from "ant-design-vue/es/form";
|
||||
|
||||
export const validateEnum = async (_rule: Rule, val: Record<any, any>[]) => {
|
||||
if (val.length === 0) return Promise.reject(new Error('请配置枚举项'));
|
||||
const flag = val.every((item) => {
|
||||
return item.value && item.text;
|
||||
});
|
||||
if (!flag) {
|
||||
return Promise.reject(new Error('请配置枚举项'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
export const validateArray = async (_rule: Rule, val: Record<any, any>) => {
|
||||
if (!val) return Promise.reject(new Error(`请输入元素配置`));
|
||||
await validateValueType(_rule, val)
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
export const validateJson = async (_rule: Rule, val: Record<any, any>[], title = '配置参数') => {
|
||||
if (!val || val.length === 0) {
|
||||
return Promise.reject(new Error(`请输入${title}`));
|
||||
}
|
||||
for (let item of val) {
|
||||
if (!item) return Promise.reject(new Error(`请输入${title}`));
|
||||
await validateIdName(_rule, item)
|
||||
await validateValueType(_rule, item.valueType)
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
export const validateIdName = async (_rule: Rule, val: Record<any, any>) => {
|
||||
if (!val.id) {
|
||||
return Promise.reject(new Error('请输入标识'))
|
||||
}
|
||||
if (!val.name) {
|
||||
return Promise.reject(new Error('请输入名称'))
|
||||
}
|
||||
}
|
||||
|
||||
export const validateValueType = async (_rule: Rule, val: Record<any, any>, title = '数据类型') => {
|
||||
console.log(val)
|
||||
if (!val) return Promise.reject(new Error('请输入元素配置'));
|
||||
if (!val?.type) {
|
||||
return Promise.reject(new Error(`请选择${title}`))
|
||||
}
|
||||
if (['enum'].includes(val.type)) {
|
||||
await validateEnum(_rule, val.elements)
|
||||
}
|
||||
if (['array'].includes(val.type)) {
|
||||
await validateArray(_rule, val.elementType)
|
||||
}
|
||||
if (['object'].includes(val.type)) {
|
||||
await validateJson(_rule, val.properties)
|
||||
}
|
||||
if (['file'].includes(val.type) && !val.fileType) {
|
||||
return Promise.reject(new Error('请选择文件类型'))
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
|
@ -38,22 +38,20 @@
|
|||
</template>
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<j-space>
|
||||
<PermissionButton :uhas-permission="`${permission}:update`" type="link" key="edit" style="padding: 0"
|
||||
<PermissionButton :has-permission="`${permission}:update`" type="link" key="edit" style="padding: 0"
|
||||
:udisabled="operateLimits('updata', type)" @click="handleEditClick(record)" :tooltip="{
|
||||
title: operateLimits('updata', type) ? '当前的存储方式不支持编辑' : '编辑',
|
||||
}">
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton :uhas-permission="`${permission}:delete`" type="link" key="delete" style="padding: 0"
|
||||
<PermissionButton :has-permission="`${permission}:delete`" type="link" key="delete" style="padding: 0"
|
||||
:pop-confirm="{
|
||||
title: '确认删除?', onConfirm: async () => {
|
||||
await removeItem(record);
|
||||
},
|
||||
}"
|
||||
:tooltip="{
|
||||
title: '删除',
|
||||
}"
|
||||
>
|
||||
}" :tooltip="{
|
||||
title: '删除',
|
||||
}">
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</j-space>
|
||||
|
@ -69,13 +67,9 @@ import { useProductStore } from '@/store/product'
|
|||
import { useMetadataStore } from '@/store/metadata'
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue'
|
||||
import { TablePaginationConfig, message } from 'ant-design-vue/es'
|
||||
import { SystemConst } from '@/utils/consts'
|
||||
import { Store } from 'jetlinks-store'
|
||||
import { asyncUpdateMetadata, removeMetadata } from '../metadata'
|
||||
import { detail } from '@/api/device/instance'
|
||||
import Edit from './Edit/index.vue'
|
||||
// import { detail } from '@/api/device/instance'
|
||||
// import { detail as productDetail } from '@/api/device/product'
|
||||
interface Props {
|
||||
type: MetadataType;
|
||||
target: 'product' | 'device';
|
||||
|
@ -134,10 +128,6 @@ const handleSearch = (searchValue: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
})
|
||||
|
||||
const refreshMetadata = () => {
|
||||
loading.value = true
|
||||
// const res = target === 'product'
|
||||
|
@ -188,11 +178,18 @@ const handleEditClick = (record: MetadataItem) => {
|
|||
}
|
||||
|
||||
const resetMetadata = async () => {
|
||||
// const { id } = route.params
|
||||
// const resp = await detail(id as string);
|
||||
// if (resp.status === 200) {
|
||||
// instanceStore.setCurrent(resp?.result || []);
|
||||
// }
|
||||
const { id } = route.params
|
||||
const resp = await detail(id as string);
|
||||
if (resp.status === 200) {
|
||||
instanceStore.setCurrent(resp?.result || []);
|
||||
if (target === 'device') {
|
||||
instanceStore.refresh(id as string)
|
||||
} else {
|
||||
productStore.refresh(id as string)
|
||||
}
|
||||
metadataStore.set('importMetadata', true)
|
||||
};
|
||||
|
||||
const removeItem = async (record: MetadataItem) => {
|
||||
|
@ -203,7 +200,7 @@ const removeItem = async (record: MetadataItem) => {
|
|||
const result = await asyncUpdateMetadata(target, _currentData);
|
||||
if (result.status === 200) {
|
||||
message.success('操作成功!');
|
||||
Store.set(SystemConst.REFRESH_METADATA_TABLE, true);
|
||||
// Store.set(SystemConst.REFRESH_METADATA_TABLE, true);
|
||||
metadataStore.model.edit = false;
|
||||
metadataStore.model.item = {};
|
||||
resetMetadata();
|
||||
|
|
|
@ -52,11 +52,30 @@
|
|||
<j-form-item
|
||||
:name="['templateDetailTable', index, 'value']"
|
||||
:rules="{
|
||||
required: true,
|
||||
required: record.required,
|
||||
message: '该字段为必填字段',
|
||||
}"
|
||||
>
|
||||
<ToUser
|
||||
v-if="record.type === 'user'"
|
||||
v-model:toUser="record.value"
|
||||
:type="data.type"
|
||||
:config-id="data.id"
|
||||
/>
|
||||
<ToOrg
|
||||
v-else-if="record.type === 'org'"
|
||||
:type="data.type"
|
||||
:config-id="data.id"
|
||||
v-model:toParty="record.value"
|
||||
/>
|
||||
<ToTag
|
||||
v-else-if="record.type === 'tag'"
|
||||
:type="data.type"
|
||||
:config-id="data.id"
|
||||
v-model:toTag="record.value"
|
||||
/>
|
||||
<ValueItem
|
||||
v-else
|
||||
v-model:modelValue="record.value"
|
||||
:itemType="record.type"
|
||||
/>
|
||||
|
@ -78,6 +97,10 @@ import type {
|
|||
} from '@/views/notice/Template/types';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import ToUser from '@/views/notice/Template/Detail/components/ToUser.vue';
|
||||
import ToOrg from '@/views/notice/Template/Detail/components/ToOrg.vue';
|
||||
import ToTag from '@/views/notice/Template/Detail/components/ToTag.vue';
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:visible', data: boolean): void;
|
||||
};
|
||||
|
@ -128,6 +151,7 @@ const getTemplateDetail = async () => {
|
|||
formData.value.templateDetailTable = result.variableDefinitions.map(
|
||||
(m: any) => ({
|
||||
...m,
|
||||
type: m.expands ? m.expands.businessType : m.type,
|
||||
value: undefined,
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -393,11 +393,11 @@ const formRules = ref({
|
|||
// webhook
|
||||
'configuration.url': [
|
||||
{ required: true, message: '请输入Webhook' },
|
||||
{
|
||||
pattern:
|
||||
/^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[j-z]{2,6}\/?/,
|
||||
message: 'Webhook需要是一个合法的URL',
|
||||
},
|
||||
// {
|
||||
// pattern:
|
||||
// /^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[j-z]{2,6}\/?/,
|
||||
// message: 'Webhook需要是一个合法的URL',
|
||||
// },
|
||||
],
|
||||
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||
});
|
||||
|
|
|
@ -156,6 +156,8 @@ watch(
|
|||
(val) => {
|
||||
if (val) {
|
||||
getDepartment();
|
||||
} else {
|
||||
dataSource.value = [];
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -357,19 +359,19 @@ const getTableData = () => {
|
|||
const bindUser = bindUsers.find(
|
||||
(f: any) => f.thirdPartyUserId === deptUser.id,
|
||||
);
|
||||
if (bindUser) {
|
||||
unBindUser = unBindUsers.find(
|
||||
(f: any) => f.id === bindUser.userId,
|
||||
);
|
||||
}
|
||||
// if (bindUser) {
|
||||
// unBindUser = unBindUsers.find(
|
||||
// (f: any) => f.id === bindUser.userId,
|
||||
// );
|
||||
// }
|
||||
dataSource.value.push({
|
||||
thirdPartyUserId: deptUser.id,
|
||||
thirdPartyUserName: deptUser.name,
|
||||
bindId: bindUser?.userId,
|
||||
userId: unBindUser?.id,
|
||||
bindId: bindUser?.id,
|
||||
userId: bindUser?.userId,
|
||||
userName: unBindUser
|
||||
? `${unBindUser.name}(${unBindUser.username})`
|
||||
: '',
|
||||
: bindUser?.providerName,
|
||||
status: {
|
||||
text: bindUser?.providerName ? '已绑定' : '未绑定',
|
||||
value: bindUser?.providerName ? 'success' : 'error',
|
||||
|
|
|
@ -11,10 +11,26 @@
|
|||
<span v-if="column.dataIndex === 'id'">
|
||||
{{ record[column.dataIndex] }}
|
||||
</span>
|
||||
<j-input
|
||||
v-if="column.dataIndex === 'name'"
|
||||
v-model:value="record.name"
|
||||
/>
|
||||
<template v-if="column.dataIndex === 'name'">
|
||||
<j-input
|
||||
v-model:value="record.name"
|
||||
:class="
|
||||
!record.name || record.name.length > 64
|
||||
? 'has-error'
|
||||
: ''
|
||||
"
|
||||
/>
|
||||
<!-- antd useForm 无table表单校验 手动添加校验 -->
|
||||
<div
|
||||
class="error-text"
|
||||
v-show="!record.name || record.name.length > 64"
|
||||
>
|
||||
<span v-show="!record.name"> 该字段是必填字段 </span>
|
||||
<span v-show="record.name.length > 64">
|
||||
最多可输入64个字符
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<j-select
|
||||
v-if="column.dataIndex === 'type'"
|
||||
v-model:value="record.type"
|
||||
|
@ -91,7 +107,7 @@ const columns = [
|
|||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
// width: 160,
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
|
@ -133,4 +149,20 @@ const handleTypeChange = (record: IVariable) => {
|
|||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.table-wrapper {
|
||||
.has-error {
|
||||
border-color: rgba(255, 77, 79);
|
||||
&:focus {
|
||||
border-color: rgba(255, 120, 117);
|
||||
box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2);
|
||||
border-right-width: 1px !important;
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
.error-text {
|
||||
color: rgba(255, 77, 79);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -526,6 +526,7 @@
|
|||
</j-form-item>
|
||||
<j-form-item
|
||||
v-if="formData.template.templateType === 'tts'"
|
||||
v-bind="validateInfos['template.ttsmessage']"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
|
@ -541,8 +542,7 @@
|
|||
</span>
|
||||
</template>
|
||||
<j-textarea
|
||||
v-model:value="formData.template.message"
|
||||
show-count
|
||||
v-model:value="formData.template.ttsmessage"
|
||||
:rows="5"
|
||||
placeholder="内容中的变量将用于阿里云语音验证码"
|
||||
/>
|
||||
|
@ -840,7 +840,8 @@ const resetPublicFiles = () => {
|
|||
formData.value.template.templateType = 'tts';
|
||||
formData.value.template.templateCode = '';
|
||||
formData.value.template.ttsCode = '';
|
||||
formData.value.template.message = '';
|
||||
// formData.value.template.message = '';
|
||||
formData.value.template.ttsmessage = '';
|
||||
formData.value.template.playTimes = 1;
|
||||
formData.value.template.calledShowNumbers = '';
|
||||
formData.value.template.calledNumber = '';
|
||||
|
@ -910,26 +911,29 @@ const formRules = ref({
|
|||
// 阿里云语音
|
||||
'template.templateType': [{ required: true, message: '请选择类型' }],
|
||||
'template.templateCode': [{ required: true, message: '请输入模板ID' }],
|
||||
'template.calledShowNumbers': [
|
||||
{
|
||||
trigger: 'change',
|
||||
validator(_rule: Rule, value: string) {
|
||||
if (!value) return Promise.resolve();
|
||||
if (!phoneRegEx(value)) return Promise.reject('请输入有效号码');
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
],
|
||||
// 短信
|
||||
'template.code': [{ required: true, message: '请选择模板' }],
|
||||
'template.signName': [{ required: true, message: '请输入签名' }],
|
||||
// webhook
|
||||
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||
'template.message': [
|
||||
{ required: true, message: '请输入' },
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
{ max: 500, message: '最多可输入500个字符' },
|
||||
],
|
||||
'template.calledShowNumbers': [
|
||||
{
|
||||
trigger: 'blur',
|
||||
validator(_rule: Rule, value: string) {
|
||||
if (!phoneRegEx(value)) {
|
||||
return Promise.reject('请输入有效号码');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
],
|
||||
'template.ttsmessage': [{ max: 500, message: '最多可输入500个字符' }],
|
||||
});
|
||||
|
||||
const { resetFields, validate, validateInfos, clearValidate } = useForm(
|
||||
|
@ -1033,6 +1037,7 @@ const variableReg = () => {
|
|||
* 钉钉机器人 消息类型选择改变
|
||||
*/
|
||||
const handleMessageTypeChange = () => {
|
||||
if (formData.value.type !== 'dingTalk') return;
|
||||
delete formData.value.template.markdown;
|
||||
delete formData.value.template.link;
|
||||
delete formData.value.template.text;
|
||||
|
@ -1159,6 +1164,13 @@ const getSignsList = async () => {
|
|||
*/
|
||||
const btnLoading = ref<boolean>(false);
|
||||
const handleSubmit = () => {
|
||||
// 变量列表存在, 且存在空值
|
||||
if (
|
||||
formData.value.variableDefinitions.length &&
|
||||
formData.value.variableDefinitions.some((s: any) => !s.name)
|
||||
)
|
||||
return;
|
||||
// 邮件没有配置字段
|
||||
if (formData.value.type === 'email') delete formData.value.configId;
|
||||
if (formData.value.template.messageType === 'markdown')
|
||||
delete formData.value.template.link;
|
||||
|
@ -1169,9 +1181,16 @@ const handleSubmit = () => {
|
|||
setTimeout(() => {
|
||||
validate()
|
||||
.then(async () => {
|
||||
if (formData.value.provider === 'ttsCode')
|
||||
if (formData.value.provider === 'aliyun') {
|
||||
formData.value.template.ttsCode =
|
||||
formData.value.template.templateCode;
|
||||
// 语音message字段与其他类型的message字段重名, 但语音不需要必填验证
|
||||
// 取别名ttsmessage, 验证通过之后, 赋值回message字段, 并删除别名字段
|
||||
formData.value.template.message =
|
||||
formData.value.template.ttsmessage;
|
||||
delete formData.value.template.ttsmessage;
|
||||
}
|
||||
|
||||
btnLoading.value = true;
|
||||
let res;
|
||||
if (!formData.value.id) {
|
||||
|
|
|
@ -60,6 +60,7 @@ export type TemplateFormData = {
|
|||
templateCode?: string;
|
||||
ttsCode?: string;
|
||||
// message?: string;
|
||||
ttsmessage?: string;
|
||||
playTimes?: number;
|
||||
calledShowNumbers?: string;
|
||||
calledNumber?: string;
|
||||
|
|
|
@ -189,7 +189,8 @@ export const TEMPLATE_FIELD_MAP = {
|
|||
templateType: 'tts',
|
||||
templateCode: '',
|
||||
ttsCode: '',
|
||||
message: '',
|
||||
// message: '',
|
||||
ttsmessage: '',
|
||||
playTimes: 1,
|
||||
calledShowNumbers: '',
|
||||
calledNumber: '',
|
||||
|
|
|
@ -13,17 +13,16 @@
|
|||
import { useSceneStore } from '@/store/scene';
|
||||
import Action from '../action/index.vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ActionsType } from '@/components/Table';
|
||||
import { ActionsType } from '@/views/rule-engine/Scene/typings';
|
||||
|
||||
const sceneStore = useSceneStore();
|
||||
const { data } = storeToRefs(sceneStore);
|
||||
|
||||
const onActionAdd = (_data: ActionsType) => {
|
||||
console.log(_data)
|
||||
// if (data?.branches && _data) {
|
||||
// const newThen = [...data?.branches?.[0].then, data];
|
||||
// data.branches[0].then = newThen;
|
||||
// }
|
||||
const onActionAdd = (_data: any) => {
|
||||
if (data.value?.branches && _data) {
|
||||
data?.value.branches?.[0].then.push(_data)
|
||||
console.log(data?.value.branches?.[0].then)
|
||||
}
|
||||
};
|
||||
|
||||
const onActionUpdate = (_data: ActionsType, type: boolean) => {
|
||||
|
|
|
@ -18,13 +18,7 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<!-- <j-card-select
|
||||
v-model:value="formModel.type"
|
||||
:options="options"
|
||||
type="horizontal"
|
||||
float="right"
|
||||
/> -->
|
||||
<a-radio-group v-model:value="formModel.type" :options="options" />
|
||||
<CardSelect v-model:value="formModel.type" :options="options"/>
|
||||
</a-form-item>
|
||||
<ActionTypeComponent
|
||||
v-bind="props"
|
||||
|
@ -46,6 +40,7 @@ import { PropType } from 'vue';
|
|||
import { ActionsType } from '../../../typings';
|
||||
import ActionTypeComponent from './ActionTypeComponent.vue';
|
||||
import { randomString } from '@/utils/utils';
|
||||
import CardSelect from '../../components/CardSelect.vue'
|
||||
|
||||
const props = defineProps({
|
||||
branchesName: {
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<!-- <j-card-select
|
||||
v-model:value="notifyType"
|
||||
:options="options"
|
||||
:icon-size="106"
|
||||
/> -->
|
||||
<a-radio-group v-model:value="notifyType" :options="options" @change="onRadioChange" />
|
||||
<div class="notify-type-warp" :class="{ disabled: disabled }">
|
||||
<div
|
||||
:key="item.id"
|
||||
v-for="item in options"
|
||||
class="notify-type-item"
|
||||
:class="{ active: notifyType === item.value }"
|
||||
@click="onSelect(item.value)"
|
||||
>
|
||||
<div class="notify-type-item-image">
|
||||
<img :width="106" :src="item.iconUrl" />
|
||||
</div>
|
||||
<div class="notify-type-item-title">{{item.label}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
|
@ -26,9 +34,13 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value'])
|
||||
const emit = defineEmits(['update:value']);
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
const notifyType = ref('');
|
||||
|
@ -37,17 +49,19 @@ const options = ref<any[]>([]);
|
|||
watch(
|
||||
() => props.value,
|
||||
(newVal) => {
|
||||
notifyType.value = newVal
|
||||
notifyType.value = newVal;
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
const onRadioChange = (e: any) => {
|
||||
emit('update:value', e.target.value)
|
||||
}
|
||||
const onSelect = (val: string) => {
|
||||
if (!props.disabled) {
|
||||
emit('update:value', val);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
notice.queryMessageType().then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
options.value = (resp.result as any[]).map((item) => {
|
||||
|
@ -58,11 +72,65 @@ onMounted(() => {
|
|||
};
|
||||
});
|
||||
}
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
});
|
||||
notifyType.value = props.value
|
||||
notifyType.value = props.value;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.notify-type-warp {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px 24px;
|
||||
width: 100%;
|
||||
|
||||
.notify-type-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 172px;
|
||||
border: 1px solid #e0e4e8;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
.notify-type-item-title {
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.notify-type-item-image {
|
||||
width: 106px;
|
||||
margin: 16px 33px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: @primary-color-hover;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: @primary-color-active;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.notify-type-item {
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
color: initial;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -26,7 +26,7 @@
|
|||
:actions="
|
||||
serialArray.length ? serialArray[0].actions : []
|
||||
"
|
||||
@add="onAdd"
|
||||
@add="(_item) => onAdd(_item, false)"
|
||||
@delete="onDelete"
|
||||
/>
|
||||
</div>
|
||||
|
@ -50,7 +50,7 @@
|
|||
? parallelArray[0].actions
|
||||
: []
|
||||
"
|
||||
@add="onAdd"
|
||||
@add="(_item) => onAdd(_item, true)"
|
||||
@delete="onDelete"
|
||||
/>
|
||||
</div>
|
||||
|
@ -98,6 +98,8 @@ watch(
|
|||
parallelArray.value = newVal.filter((item) => item.parallel);
|
||||
serialArray.value = newVal.filter((item) => !item.parallel);
|
||||
|
||||
console.log(parallelArray.value, serialArray.value, '123')
|
||||
|
||||
const isSerialActions = serialArray.value.some((item) => {
|
||||
return !!item.actions.length;
|
||||
});
|
||||
|
@ -128,7 +130,7 @@ const onDelete = (_key: string) => {
|
|||
emit('update', serialArray[0], false);
|
||||
}
|
||||
};
|
||||
const onAdd = (actionItem: any) => {
|
||||
const onAdd = (actionItem: any, _parallel: boolean) => {
|
||||
const newParallelArray = [...parallelArray.value];
|
||||
if (newParallelArray.length) {
|
||||
const indexOf = newParallelArray[0].actions?.findIndex(
|
||||
|
@ -140,12 +142,11 @@ const onAdd = (actionItem: any) => {
|
|||
newParallelArray[0].actions.push(actionItem);
|
||||
}
|
||||
parallelArray.value = [...newParallelArray];
|
||||
console.log(parallelArray.value);
|
||||
emit('update', newParallelArray[0], true);
|
||||
emit('update', newParallelArray[0], _parallel);
|
||||
} else {
|
||||
actionItem.key = randomString();
|
||||
emit('add', {
|
||||
parallel: true,
|
||||
parallel: _parallel,
|
||||
key: randomString(),
|
||||
actions: [actionItem],
|
||||
});
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
<template>
|
||||
<div class="scene-trigger-way-warp" :class="{disabled: disabled}">
|
||||
<template v-for="item in options" :key="item.value">
|
||||
<div
|
||||
class="trigger-way-item"
|
||||
:class="{ active: item?.value === value }"
|
||||
:style="{width: `${cardSize}px`}"
|
||||
@click="onSelect(item.value)"
|
||||
>
|
||||
<div class="way-item-title">
|
||||
<p>{{ item.label }}</p>
|
||||
<span>{{ item.subLabel }}</span>
|
||||
</div>
|
||||
<div class="way-item-image">
|
||||
<img :src="item.iconUrl" :width="imageSize" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
options: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => [],
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<'vertical' | 'horizontal'>,
|
||||
default: 'horizontal',
|
||||
},
|
||||
imageSize: {
|
||||
type: Number,
|
||||
default: 48
|
||||
},
|
||||
cardSize: {
|
||||
type: Number,
|
||||
default: 237
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value'])
|
||||
|
||||
const onSelect = (_type: string) => {
|
||||
if (!props.disabled) {
|
||||
emit('update:value', _type)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.scene-trigger-way-warp {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px 24px;
|
||||
width: 100%;
|
||||
|
||||
.trigger-way-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
border: 1px solid #e0e4e8;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
.way-item-title {
|
||||
p {
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: rgba(#000, 0.35);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.way-item-image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: @primary-color-hover;
|
||||
.way-item-image {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: @primary-color-active;
|
||||
.way-item-image {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.trigger-way-item {
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
color: initial;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,8 +1,17 @@
|
|||
<template>
|
||||
<j-dropdown class='scene-select' trigger='click'>
|
||||
<div :class='dropdownButtonClass'>
|
||||
<AIcon v-if='!!icon' :type='icon' />
|
||||
{{ label }}
|
||||
<j-dropdown
|
||||
class='scene-select'
|
||||
trigger='click'
|
||||
v-model:visible='visible'
|
||||
@visibleChange='visibleChange'
|
||||
>
|
||||
<div @click.prevent='visible = true'>
|
||||
<slot :label='label'>
|
||||
<div :class='dropdownButtonClass' >
|
||||
<AIcon v-if='!!icon' :type='icon' />
|
||||
{{ label }}
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<div class='scene-select-content'>
|
||||
|
@ -18,7 +27,7 @@
|
|||
:type='component'
|
||||
@change='timeSelect'
|
||||
/>
|
||||
<div v-else>
|
||||
<div style='min-width: 400px' v-else>
|
||||
<j-tree
|
||||
:selectedKeys='selectValue ? [selectValue] : []'
|
||||
:treeData='options'
|
||||
|
@ -89,20 +98,18 @@ const props = defineProps({
|
|||
component: {
|
||||
type: String,
|
||||
default: 'select' // 'select' | 'treeSelect'
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const label = ref<LabelType>(props.placeholder)
|
||||
const selectValue = ref(props.value)
|
||||
const flatMapTree = new Map()
|
||||
const visible = ref(false)
|
||||
|
||||
const LabelStyle = computed(() => {
|
||||
return {
|
||||
color: selectValue.value ? '#' : '#'
|
||||
}
|
||||
})
|
||||
const visibleChange = (v: boolean) => {
|
||||
visible.value = v
|
||||
}
|
||||
|
||||
const dropdownButtonClass = computed(() => ({
|
||||
'dropdown-button': true,
|
||||
|
@ -112,24 +119,33 @@ const dropdownButtonClass = computed(() => ({
|
|||
'type': props.type === 'type',
|
||||
}))
|
||||
|
||||
const treeSelect = (v: any) => {
|
||||
|
||||
const treeSelect = (v: any, option: any) => {
|
||||
const node = option.node
|
||||
visible.value = false
|
||||
label.value = node.fullname || node.name
|
||||
selectValue.value = v[0]
|
||||
emit('update:value', node[props.valueName])
|
||||
emit('select', node)
|
||||
}
|
||||
|
||||
const timeSelect = (v: string) => {
|
||||
selectValue.value = v
|
||||
visible.value = false
|
||||
emit('update:value', v)
|
||||
emit('select', v)
|
||||
}
|
||||
|
||||
const menuSelect = (v: any) => {
|
||||
const option = getOption(props.options, props.value, props.valueName)
|
||||
emit('update:value', v.key)
|
||||
const menuSelect = (v: string, option: any) => {
|
||||
selectValue.value = v
|
||||
visible.value = false
|
||||
emit('update:value', v)
|
||||
emit('select', option)
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
const option = getOption(props.options, props.value, props.valueName)
|
||||
if (option && Object.keys(option).length) {
|
||||
selectValue.value = props.value
|
||||
if (option) {
|
||||
label.value = option[props.labelName] || option.name
|
||||
} else {
|
||||
label.value = props.value || props.placeholder
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
</template>
|
||||
|
||||
<script lang='ts' setup name='DropdownMenus'>
|
||||
import { isBoolean } from 'lodash-es'
|
||||
import { isBoolean, isUndefined } from 'lodash-es'
|
||||
import { getOption } from '../DropdownButton/util'
|
||||
|
||||
type ValueType = string| number | boolean
|
||||
type Emits = {
|
||||
(e: 'update:value', value: ValueType): void
|
||||
(e: 'click', data: any): void
|
||||
(e: 'click', value: string, data: any): void
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
|
@ -32,29 +32,39 @@ const emit = defineEmits<Emits>()
|
|||
const myOptions = computed(() => {
|
||||
return props.options.map((item: any) => {
|
||||
let _label = item.label || item.name
|
||||
if (isBoolean(item.value)) {
|
||||
_label = item.value === true ? '是' : '否'
|
||||
let _value = isUndefined(item.value) ? item.id : item.value
|
||||
if (isBoolean(_value)) {
|
||||
_label = _value === true ? '是' : '否'
|
||||
_value = String(_value)
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
label: _label,
|
||||
value: item.value || item.id,
|
||||
value: _value
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const myValue = ref(props.value)
|
||||
|
||||
const handleBoolean = (key: string) => {
|
||||
return key === 'false' ? false : true
|
||||
}
|
||||
|
||||
const click = (e: any) => {
|
||||
const option = getOption(myOptions.value, e.key)
|
||||
myValue.value = e.key
|
||||
emit('update:value', e.key)
|
||||
emit('click', {
|
||||
key: e.key,
|
||||
const _key = ['true', 'false'].includes(e.key) ? handleBoolean(e.key) : e.key
|
||||
const option = getOption(myOptions.value, _key)
|
||||
myValue.value = _key
|
||||
emit('update:value', _key)
|
||||
emit('click', _key, {
|
||||
key: _key,
|
||||
...option
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => props.value, () => {
|
||||
myValue.value = props.value
|
||||
}, { immediate: true})
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
|
|
|
@ -62,7 +62,7 @@ const change = (e: string) => {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style lang='less'>
|
||||
<style lang='less' scoped>
|
||||
.dropdown-time-picker {
|
||||
>div{
|
||||
position: relative !important;
|
||||
|
@ -86,4 +86,8 @@ const change = (e: string) => {
|
|||
box-shadow: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper{
|
||||
display: none;
|
||||
}
|
||||
</style>
|
|
@ -26,8 +26,8 @@ export const getComponent = (type: string): string => {
|
|||
}
|
||||
|
||||
export const getOption = (data: any[], value?: string | number | boolean, key: string = 'name'): DropdownButtonOptions | any => {
|
||||
let option = {}
|
||||
if (!value) return option
|
||||
let option
|
||||
if (value === undefined && value === null) return option
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const item = data[i]
|
||||
if (item[key] === value) {
|
||||
|
@ -35,7 +35,9 @@ export const getOption = (data: any[], value?: string | number | boolean, key: s
|
|||
break
|
||||
} else if (item.children && item.children.length){
|
||||
option = getOption(item.children, value, key)
|
||||
if (option) break
|
||||
if (option) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return option
|
||||
|
|
|
@ -5,9 +5,13 @@
|
|||
v-model:visible='visible'
|
||||
@visibleChange='visibleChange'
|
||||
>
|
||||
<div class='dropdown-button value' @click.prevent='visible = true'>
|
||||
<AIcon v-if='!!icon' :type='icon' />
|
||||
{{ label }}
|
||||
<div @click.prevent='visible = true'>
|
||||
<slot :label='label'>
|
||||
<div class='dropdown-button value'>
|
||||
<AIcon v-if='!!icon' :type='icon' />
|
||||
{{ label }}
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<div class='scene-select-content'>
|
||||
|
@ -24,32 +28,36 @@
|
|||
@change='timeChange'
|
||||
/>
|
||||
<DropdownMenus
|
||||
v-if='["metric","enum", "boolean"].includes(item.component)'
|
||||
:options='options'
|
||||
@change='onSelect'
|
||||
v-if='["select","enum", "boolean"].includes(item.component)'
|
||||
:options='["metric", "upper"].includes(item.key) ? metricOption : options'
|
||||
@click='onSelect'
|
||||
/>
|
||||
<div
|
||||
v-else-if='item.component === "tree"'
|
||||
style='min-width: 400px'
|
||||
>
|
||||
<j-tree
|
||||
:selectedKeys='myValue ? [myValue] : []'
|
||||
:treeData='item.key === "upper" ? metricOption : options'
|
||||
@select='treeSelect'
|
||||
:height='450'
|
||||
:virtual='true'
|
||||
>
|
||||
<template #title="{ name, description }">
|
||||
<j-space>
|
||||
{{ name }}
|
||||
<span v-if='description' class='tree-title-description'>{{ description }}</span>
|
||||
</j-space>
|
||||
</template>
|
||||
</j-tree>
|
||||
</div>
|
||||
<ValueItem
|
||||
v-else-if='valueItemKey.includes(item.component)'
|
||||
v-else
|
||||
v-model:modelValue='myValue'
|
||||
:itemType='getComponent(item.component)'
|
||||
:options='options'
|
||||
:options='item.key === "upper" ? metricOption : options'
|
||||
@change='valueItemChange'
|
||||
/>
|
||||
<j-tree
|
||||
v-else
|
||||
:selectedKeys='myValue ? [myValue] : []'
|
||||
:treeData='options'
|
||||
@select='treeSelect'
|
||||
:height='450'
|
||||
:virtual='true'
|
||||
>
|
||||
<template #title="{ name, description }">
|
||||
<j-space>
|
||||
{{ name }}
|
||||
<span v-if='description' class='tree-title-description'>{{ description }}</span>
|
||||
</j-space>
|
||||
</template>
|
||||
</j-tree>
|
||||
</div>
|
||||
</j-tab-pane>
|
||||
</j-tabs>
|
||||
|
@ -101,10 +109,9 @@ const updateValue = () => {
|
|||
}
|
||||
|
||||
const treeSelect = (e: any) => {
|
||||
console.log('treeSelect', e)
|
||||
visible.value = false
|
||||
label.value = e.fullname || e.name
|
||||
emit('update:value', e.id)
|
||||
emit('update:value', e[props.valueName])
|
||||
emit('select', e)
|
||||
}
|
||||
|
||||
|
@ -115,7 +122,8 @@ const valueItemChange = (e: string) => {
|
|||
emit('select', e)
|
||||
}
|
||||
|
||||
const sonSelect = (e: string, option: any) => {
|
||||
const onSelect = (e: string, option: any) => {
|
||||
console.log(e, option)
|
||||
visible.value = false
|
||||
label.value = option.label
|
||||
emit('update:value', e)
|
||||
|
@ -133,14 +141,16 @@ const visibleChange = (v: boolean) => {
|
|||
visible.value = v
|
||||
}
|
||||
|
||||
watch([props.options, props.value], () => {
|
||||
watchEffect(() => {
|
||||
const option = getOption(props.options, props.value as string, props.valueName) // 回显label值
|
||||
if (option && Object.keys(option).length) {
|
||||
myValue.value = props.value
|
||||
mySource.value = props.source
|
||||
if (option) {
|
||||
label.value = option[props.labelName] || option.name
|
||||
} else {
|
||||
label.value = props.value || props.placeholder
|
||||
}
|
||||
}, { immediate: true })
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
|
|
@ -12,8 +12,7 @@ export type DropdownButtonOptions = {
|
|||
export type TabsOption = {
|
||||
label: string;
|
||||
key: string;
|
||||
component: string,
|
||||
options: DropdownButtonOptions[]
|
||||
component: string
|
||||
}
|
||||
type ValueArrayType = [string, number]
|
||||
export type ValueType = string | number | undefined | ValueArrayType
|
||||
|
@ -47,6 +46,10 @@ export const defaultSetting = {
|
|||
type: Array as PropType<Array<DropdownButtonOptions>>,
|
||||
default: () => []
|
||||
},
|
||||
metricOption: {
|
||||
type: Array as PropType<Array<DropdownButtonOptions>>,
|
||||
default: () => []
|
||||
},
|
||||
metricOptions: { // 指标值
|
||||
type: Array as PropType<Array<DropdownButtonOptions>>,
|
||||
default: () => []
|
||||
|
|
|
@ -40,10 +40,8 @@
|
|||
icon='icon-canshu'
|
||||
placeholder='参数值'
|
||||
:options='valueOptions'
|
||||
:tabsOptions='[
|
||||
{ label: "手动输入", component: "input", key: "fixed" },
|
||||
{ label: "指标值", component: "time", key: "manual" }
|
||||
]'
|
||||
:metricOption='metricOption'
|
||||
:tabsOptions='tabsOptions'
|
||||
v-model:value='paramsValue.value.value'
|
||||
v-model:source='paramsValue.value.source'
|
||||
/>
|
||||
|
@ -52,10 +50,8 @@
|
|||
icon='icon-canshu'
|
||||
placeholder='参数值'
|
||||
:options='valueOptions'
|
||||
:tabsOptions='[
|
||||
{ label: "手动输入", component: "time", key: "fixed" },
|
||||
{ label: "指标值", component: "input", key: "manual" },
|
||||
]'
|
||||
:metricOption='metricOption'
|
||||
:tabsOptions='tabsOptions'
|
||||
v-model:value='paramsValue.value.value'
|
||||
v-model:source='paramsValue.value.source'
|
||||
/>
|
||||
|
@ -80,6 +76,16 @@ import ParamsDropdown, { DoubleParamsDropdown } from '../ParamsDropdown'
|
|||
import { inject } from 'vue'
|
||||
import { ContextKey } from './util'
|
||||
|
||||
type Emit = {
|
||||
(e: 'update:value', data: TermsType): void
|
||||
}
|
||||
|
||||
type TabsOption = {
|
||||
label: string;
|
||||
key: string;
|
||||
component: string
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
isFirst: {
|
||||
type: Boolean,
|
||||
|
@ -98,7 +104,7 @@ const props = defineProps({
|
|||
default: () => ({
|
||||
column: '',
|
||||
type: '',
|
||||
termType: undefined,
|
||||
termType: 'eq',
|
||||
value: {
|
||||
source: 'fixed',
|
||||
value: undefined
|
||||
|
@ -107,6 +113,8 @@ const props = defineProps({
|
|||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const paramsValue = reactive<TermsType>({
|
||||
column: props.value.column,
|
||||
type: props.value.type,
|
||||
|
@ -115,21 +123,56 @@ const paramsValue = reactive<TermsType>({
|
|||
})
|
||||
|
||||
const showDelete = ref(false)
|
||||
const columnOptions: any = inject(ContextKey)
|
||||
const options = ref<any>([])
|
||||
const columnOptions: any = inject(ContextKey) //
|
||||
const termTypeOptions = ref<Array<{ id: string, name: string}>>([]) // 条件值
|
||||
const valueOptions = ref<any[]>([]) // 默认手动输入下拉
|
||||
const metricOption = ref<any[]>([]) // 根据termType获取对应指标值
|
||||
const tabsOptions = ref<Array<TabsOption>>([{ label: '手动输入', key: 'manual', component: 'string' }])
|
||||
let metricsCacheOption: any[] = [] // 缓存指标值
|
||||
|
||||
const termTypeOptions = computed(() => {
|
||||
const handOptionByColumn = (option: any) => {
|
||||
if (option) {
|
||||
termTypeOptions.value = option.termTypes || []
|
||||
metricsCacheOption = option.metrics || []
|
||||
tabsOptions.value.length = 1
|
||||
tabsOptions.value[0].component = option.dataType
|
||||
|
||||
if (option.metrics && option.metrics.length) {
|
||||
tabsOptions.value.push(
|
||||
{ label: '指标值', key: 'metric', component: 'select' }
|
||||
)
|
||||
}
|
||||
|
||||
if (option.dataType === 'boolean') {
|
||||
valueOptions.value = [
|
||||
{ label: '是', value: true },
|
||||
{ label: '否', value: false },
|
||||
]
|
||||
} else if(option.dataType === 'enum') {
|
||||
valueOptions.value = option.options?.map((item: any) => ({ ...item, label: item.name, value: item.id})) || []
|
||||
} else{
|
||||
valueOptions.value = option.options || []
|
||||
}
|
||||
} else {
|
||||
termTypeOptions.value = []
|
||||
metricsCacheOption = []
|
||||
valueOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
const option = getOption(columnOptions.value, paramsValue.column, 'column')
|
||||
return option && Object.keys(option).length ? option.termTypes : []
|
||||
})
|
||||
|
||||
const tabsOptions = computed(() => {
|
||||
// 获取当前value对应的option
|
||||
return []
|
||||
handOptionByColumn(option)
|
||||
})
|
||||
|
||||
const showDouble = computed(() => {
|
||||
return paramsValue.termType ? ['nbtw', 'btw', 'in', 'nin'].includes(paramsValue.termType) : false
|
||||
const isRange = paramsValue.termType ? ['nbtw', 'btw', 'in', 'nin'].includes(paramsValue.termType) : false
|
||||
if (metricsCacheOption.length) {
|
||||
metricOption.value = metricsCacheOption.filter(item => isRange ? item.range : !item.range)
|
||||
} else {
|
||||
metricOption.value = []
|
||||
}
|
||||
return isRange
|
||||
})
|
||||
|
||||
const mouseover = () => {
|
||||
|
@ -145,11 +188,20 @@ const mouseout = () => {
|
|||
}
|
||||
|
||||
const columnSelect = () => {
|
||||
|
||||
paramsValue.termType = 'eq'
|
||||
paramsValue.value = {
|
||||
source: tabsOptions.value[0].key,
|
||||
value: undefined
|
||||
}
|
||||
emit('update:value', { ...paramsValue })
|
||||
}
|
||||
|
||||
const termsTypeSelect = () => {
|
||||
|
||||
paramsValue.value = {
|
||||
source: tabsOptions.value[0].key,
|
||||
value: undefined
|
||||
}
|
||||
emit('update:value', { ...paramsValue })
|
||||
}
|
||||
|
||||
const termAdd = () => {
|
||||
|
@ -160,10 +212,6 @@ const onDelete = () => {
|
|||
|
||||
}
|
||||
|
||||
const valueOptions = computed(() => {
|
||||
return []
|
||||
})
|
||||
|
||||
nextTick(() => {
|
||||
Object.assign(paramsValue, props.value)
|
||||
})
|
||||
|
|
|
@ -77,11 +77,22 @@ const rules = [{
|
|||
}
|
||||
}]
|
||||
|
||||
const handleParamsData = (data: any[]): any[] => {
|
||||
return data?.map(item => {
|
||||
return {
|
||||
...item,
|
||||
key: item.column,
|
||||
disabled: !!item.children,
|
||||
children: handleParamsData(item.children)
|
||||
}
|
||||
}) || []
|
||||
}
|
||||
|
||||
const queryColumn = (dataModel: FormModelType) => {
|
||||
const cloneDevice = cloneDeep(dataModel)
|
||||
cloneDevice.branches = cloneDevice.branches?.filter(item => !!item)
|
||||
getParseTerm(cloneDevice).then(res => {
|
||||
columnOptions.value = res.result
|
||||
columnOptions.value = handleParamsData(res.result as any[])
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -121,7 +121,7 @@
|
|||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts'>
|
||||
<script setup lang='ts' name='Scene'>
|
||||
import SaveModal from './Save/save.vue';
|
||||
import type { SceneItem } from './typings';
|
||||
import { useMenuStore } from 'store/menu';
|
||||
|
|
|
@ -187,7 +187,7 @@ export type TriggerType = {
|
|||
};
|
||||
|
||||
export interface TermsVale {
|
||||
source: keyof typeof Source;
|
||||
source: string;
|
||||
/** 手动输入值,source为 manual 时不能为空 */
|
||||
value?: Record<string, any> | any[];
|
||||
/** 指标值,source为 metric 时不能为空 */
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-spin :spinning="loading" :delay="500">
|
||||
<j-spin :spinning="loading" :delay="500">
|
||||
<div class="container">
|
||||
<div class="left">
|
||||
<img
|
||||
|
@ -32,13 +32,13 @@
|
|||
{{ basis.title || SystemConst.SYSTEM_NAME }}
|
||||
</div>
|
||||
<div class="main">
|
||||
<a-form
|
||||
<j-form
|
||||
layout="vertical"
|
||||
:model="form"
|
||||
class="login-form"
|
||||
@finish="onFinish"
|
||||
>
|
||||
<a-form-item
|
||||
<j-form-item
|
||||
label="账号"
|
||||
name="username"
|
||||
:rules="[
|
||||
|
@ -48,13 +48,13 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="form.username"
|
||||
placeholder="请输入账号"
|
||||
:maxlength="64"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
></j-input>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
label="密码"
|
||||
name="password"
|
||||
:rules="[
|
||||
|
@ -64,13 +64,13 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input-password
|
||||
<j-input-password
|
||||
v-model:value="form.password"
|
||||
placeholder="请输入密码"
|
||||
:maxlength="64"
|
||||
></a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
></j-input-password>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
v-if="codeConfig"
|
||||
class="verifyCode"
|
||||
label="验证码"
|
||||
|
@ -82,7 +82,7 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="form.verifyCode"
|
||||
autocomplete="off"
|
||||
:maxlength="64"
|
||||
|
@ -96,13 +96,13 @@
|
|||
/>
|
||||
</div>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
</j-input>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
name="remember"
|
||||
style="text-align: left"
|
||||
>
|
||||
<a-checkbox
|
||||
<j-checkbox
|
||||
v-model:checked="form.remember"
|
||||
@change="
|
||||
() =>
|
||||
|
@ -111,10 +111,10 @@
|
|||
? -1
|
||||
: 3600000)
|
||||
"
|
||||
>记住我</a-checkbox
|
||||
>记住我</j-checkbox
|
||||
>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
</j-form-item>
|
||||
<j-form-item>
|
||||
<j-button
|
||||
:loading="loading"
|
||||
type="primary"
|
||||
|
@ -124,14 +124,14 @@
|
|||
>
|
||||
登录
|
||||
</j-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
<div class="other">
|
||||
<a-divider plain>
|
||||
<j-divider plain>
|
||||
<div class="other-text">
|
||||
其他方式登录
|
||||
</div>
|
||||
</a-divider>
|
||||
</j-divider>
|
||||
<div class="other-button">
|
||||
<j-button
|
||||
v-for="(item, index) in bindings"
|
||||
|
@ -173,7 +173,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</j-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -3695,8 +3695,8 @@ jetlinks-store@^0.0.3:
|
|||
|
||||
jetlinks-ui-components@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.4.tgz#cd080b2d8320c7a03ed20f4c4c22e9a5c920a3c8"
|
||||
integrity sha512-8xzDFH6jSGNrkbCMb+/FL/g9vB0c2GdsDq6QPUzgKFwzPiS+cPbpSxL0RNHez9MF7/NSSuY8wL4VaujiVXTpfg==
|
||||
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.4.tgz#41d52892f0f4d38adc6df02a87290a3042eb5645"
|
||||
integrity sha512-aX+XiGigzxZnrG52xqipxd+WuFwBeZ6+dvLkcvOfLLBqSu8sgfvr/8NJ5hFgv5Eo2QFnUJq3Qf4HXLw9Ogv/yw==
|
||||
dependencies:
|
||||
"@vueuse/core" "^9.12.0"
|
||||
ant-design-vue "^3.2.15"
|
||||
|
|
Loading…
Reference in New Issue