Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev

This commit is contained in:
JiangQiming 2023-03-23 11:48:46 +08:00
commit e9d5c8e9c3
69 changed files with 862 additions and 519 deletions

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -1,5 +1,10 @@
<template>
<ConfigProvider :locale='zhCN'>
<!-- <router-view v-slot="{ Component }">-->
<!-- <keep-alive>-->
<!-- <component :is="Component" />-->
<!-- </keep-alive>-->
<!-- </router-view>-->
<router-view />
</ConfigProvider>
</template>

View File

@ -6,6 +6,7 @@
v-model:selectedKeys="state.selectedKeys"
:pure="state.pure"
:breadcrumb="{ routes: breadcrumb }"
@backClick='routerBack'
>
<template #breadcrumbRender="slotProps">
<a
@ -70,6 +71,10 @@ const state = reactive<StateType>({
selectedKeys: [],
});
const routerBack = () => {
router.go(-1)
}
const findRouteMeta = (code: string) => {
let meta = []
let menuItem: any = menu.menus[code]

View File

@ -194,7 +194,7 @@ const timeChange = (e: any) => {
}
const inputChange = (e: any) => {
emit('change', e.target ? e.target.value : e)
emit('change', e && e.target ? e.target.value : e)
}
const dateChange = (e: any) => {

View File

@ -51,7 +51,7 @@ export const AccountMenu = {
export default [
{ path: '/*', redirect: '/'},
{
path: '/login',
path: LoginPath,
component: () => import('@/views/user/Login/index.vue')
},
{

View File

@ -155,10 +155,11 @@ const errorHandler = (error: any) => {
Notification.error({
key: '401',
message: 'Unauthorized',
description: 'Authorization verification failed'
description: '用户未登录'
})
setTimeout(() => {
router.replace({
debugger
router.push({
path: LoginPath
})
}, 0)

View File

@ -16,6 +16,9 @@
moment(slotProps.requestTime).format('YYYY-MM-DD HH:mm:ss')
}}
</template>
<template #description="slotProps">
{{ slotProps.action }}
</template>
<template #responseTime="slotProps">
<j-tag color="purple">
{{ slotProps.responseTime - slotProps.requestTime }} ms
@ -59,12 +62,7 @@
</template>
</j-pro-table>
</div>
<j-modal
:width="1100"
v-model:visible="visible"
title="详情"
@ok="handleOk"
>
<j-modal :width="1100" v-model:visible="visible" title="详情">
<j-descriptions :data="descriptionsData" title="" bordered :column="2">
<j-descriptions-item label="URL">
{{ descriptionsData?.url }}
@ -112,6 +110,9 @@
/>
</j-descriptions-item>
</j-descriptions>
<template #footer>
<j-button type="primary" @click="handleOk">关闭</j-button>
</template>
</j-modal>
</template>
<script lang="ts" setup name="AccessLog">
@ -145,6 +146,16 @@ const columns = [
},
ellipsis: true,
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
scopedSlots: true,
search: {
type: 'string',
},
ellipsis: true,
},
{
title: '请求方法',
dataIndex: 'httpMethod',
@ -198,9 +209,9 @@ const columns = [
title: '请求用户',
dataIndex: 'username',
key: 'username',
search: {
type: 'string',
},
// search: {
// type: 'string',
// },
width: 150,
scopedSlots: true,
},

View File

@ -66,12 +66,7 @@
</template>
</j-pro-table>
</div>
<j-modal
:width="1100"
v-model:visible="visible"
title="详情"
@ok="handleOk"
>
<j-modal :width="1100" v-model:visible="visible" title="详情">
<div>
<span class="mr-10">[{{ descriptionsData?.threadName }}]</span>
<span class="mr-10">{{
@ -102,6 +97,9 @@
placeholder="暂无数据"
:auto-size="{ minRows: 24, maxRows: 28 }"
/>
<template #footer>
<j-button type="primary" @click="handleOk">关闭</j-button>
</template>
</j-modal>
</template>
<script lang="ts" setup name="SystemLog">

View File

@ -19,6 +19,7 @@
format="YYYY-MM-DD HH:mm:ss"
valueFormat="YYYY-MM-DD HH:mm:ss"
style="margin-left: 12px"
:show-time="{ format: 'HH:mm:ss' }"
@change="rangeChange"
v-model:value="rangeVal"
:allowClear="false"
@ -110,5 +111,5 @@ const handleBtnChange = (val: string) => {
});
};
handleBtnChange(radioValue.value);
watch(() => radioValue.value, { deep: true, immediate: true });
// watch(() => radioValue.value, { deep: true, immediate: true });
</script>

View File

@ -364,7 +364,7 @@ const setDevMesChartOption = (
grid: {
top: '2%',
bottom: '5%',
left: maxY > 100000 ? '90px' : '60px',
left: maxY > 100000 ? '50px' : '70px',
right: '50px',
},
series: [

View File

@ -237,6 +237,8 @@ const emit = defineEmits(['change']);
const id = props.data.id;
const VersionOrder = props.data.versionOrder;
const VersionSign = props.data.sign;
const VersionUrl = props.data.url;
const formData: any = ref({
name: '',
@ -254,12 +256,14 @@ const extraValue: any = ref({});
const validatorSign = async (_: Record<string, any>, value: string) => {
const { signMethod, url } = formData.value;
if (value && !!signMethod && !!url && !extraValue.value) {
if (id && VersionSign === value && VersionUrl === url) {
return Promise.resolve();
} else {
if (value && !!signMethod && !!url && !!extraValue.value) {
return extraValue.value[signMethod] !== value
? Promise.reject('签名不一致,请检查文件是否上传正确')
: Promise.resolve();
} else {
return Promise.resolve();
}
}
};
const validatorVersionOrder = async (_: Record<string, any>, value: string) => {
@ -270,7 +274,9 @@ const validatorVersionOrder = async (_: Record<string, any>, value: string) => {
if (value && !!signMethod && productId) {
const res = await validateVersion(productId, value);
if (res.status === 200) {
return Promise.reject(res.result ? '版本序号已存在' : '');
return res.result
? Promise.reject('版本序号已存在')
: Promise.resolve();
}
}
}

View File

@ -280,7 +280,7 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
if (!data) {
return [];
}
const Actions = [
const Actions: any = [
{
key: 'view',
text: '查看',
@ -292,7 +292,11 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
handlEye(data.errorReason);
},
},
{
];
const { state, mode } = data;
if (mode.value === 'push' && state.value === 'failed') {
Actions.push({
key: 'update',
text: '重试',
tooltip: {
@ -305,8 +309,9 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
handlTry(data.id);
},
},
},
];
});
}
return Actions;
};

View File

@ -208,10 +208,4 @@ watch(
);
</script>
<style lang="less" scoped>
.form {
.form-submit {
background-color: @primary-color !important;
}
}
</style>
<style lang="less" scoped></style>

View File

@ -143,7 +143,7 @@ const columns = [
key: 'createTime',
dataIndex: 'createTime',
search: {
type: 'time',
type: 'date',
},
width: 200,
scopedSlots: true,
@ -197,6 +197,9 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
{
key: 'delete',
text: '删除',
tooltip: {
title: '删除',
},
popConfirm: {
title: '确认删除?',
okText: ' 确定',

View File

@ -147,6 +147,10 @@ const columns = [
dataIndex: 'id',
key: 'id',
ellipsis: true,
search:{
type:'string',
defaultTermType: 'eq'
}
},
{
title: '设备名称',

View File

@ -1,21 +1,16 @@
<template>
<page-container
:tabList="list"
:showBack="true"
:tabActiveKey="instanceStore.tabActiveKey"
@tabChange="onTabChange"
>
<template #title>
<div>
<div style="display: flex; align-items: center">
<!-- <j-button @click="onBack" size="small">返回</j-button> -->
<div style="font-size: 24px">
{{ instanceStore.current?.name }}
</div>
<j-divider type="vertical" />
<j-space>
<span
style="font-size: 14px; color: rgba(0, 0, 0, 0.85)"
>
<span style="font-size: 14px; color: rgba(0, 0, 0, 0.85)">
状态
<j-badge
:status="
@ -28,8 +23,7 @@
</span>
<PermissionButton
v-if="
instanceStore.current?.state?.value ===
'notActive'
instanceStore.current?.state?.value === 'notActive'
"
type="link"
style="margin-top: -5px; padding: 0 20px"
@ -42,9 +36,7 @@
启用设备
</PermissionButton>
<PermissionButton
v-if="
instanceStore.current?.state?.value === 'online'
"
v-if="instanceStore.current?.state?.value === 'online'"
type="link"
style="margin-top: -5px; padding: 0 20px"
:popConfirm="{
@ -59,8 +51,7 @@
v-if="
instanceStore.current?.accessProvider ===
'child-device' &&
instanceStore.current?.state?.value ===
'offline'
instanceStore.current?.state?.value === 'offline'
"
:title="
instanceStore.current?.features?.find(
@ -77,7 +68,8 @@
</j-tooltip>
</j-space>
</div>
<div style="padding-top: 24px">
</template>
<template #content>
<j-descriptions size="small" :column="4">
<j-descriptions-item label="ID">{{
instanceStore.current?.id
@ -93,8 +85,6 @@
</PermissionButton>
</j-descriptions-item>
</j-descriptions>
</div>
</div>
</template>
<template #extra>
<img
@ -142,7 +132,7 @@ statusMap.set('notActive', 'warning');
const statusRef = ref();
const list = ref([
const initList = [
{
key: 'Info',
tab: '实例信息',
@ -163,7 +153,9 @@ const list = ref([
key: 'Log',
tab: '日志管理',
},
]);
];
const list = ref([...initList]);
const tabs = {
Info,
@ -191,64 +183,7 @@ const getStatus = (id: string) => {
});
};
watch(
() => route.params?.id,
(newId) => {
if (newId) {
instanceStore.refresh(String(newId));
getStatus(String(newId));
instanceStore.tabActiveKey = 'Info'
}
},
{ immediate: true, deep: true },
);
onMounted(() => {
instanceStore.tabActiveKey = history.state?.params?.tab || 'Info';
});
// const onBack = () => {
// menuStory.jumpPage('device/Instance');
// };
const onTabChange = (e: string) => {
instanceStore.tabActiveKey = e;
};
const handleAction = async () => {
if (instanceStore.current?.id) {
const resp = await _deploy(instanceStore.current?.id);
if (resp.status === 200) {
message.success('操作成功!');
instanceStore.refresh(instanceStore.current?.id);
}
}
};
const handleDisconnect = async () => {
if (instanceStore.current?.id) {
const resp = await _disconnect(instanceStore.current?.id);
if (resp.status === 200) {
message.success('操作成功!');
instanceStore.refresh(instanceStore.current?.id);
}
}
};
const handleRefresh = async () => {
if (instanceStore.current?.id) {
await instanceStore.refresh(instanceStore.current?.id);
message.success('操作成功');
}
};
const jumpProduct = () => {
menuStory.jumpPage('device/Product/Detail', {
id: instanceStore.current?.productId,
});
};
watchEffect(() => {
const getDetail = () => {
const keys = list.value.map((i) => i.key);
if (
instanceStore.current?.protocol &&
@ -309,8 +244,67 @@ watchEffect(() => {
tab: '边缘端映射',
});
}
};
watch(
() => route.params?.id,
async (newId) => {
if (newId) {
await instanceStore.refresh(String(newId));
getStatus(String(newId));
list.value = [...initList];
getDetail();
instanceStore.tabActiveKey = 'Info';
}
},
{ immediate: true, deep: true },
);
onMounted(() => {
instanceStore.tabActiveKey = history.state?.params?.tab || 'Info';
});
const onBack = () => {
menuStory.jumpPage('device/Instance');
};
const onTabChange = (e: string) => {
instanceStore.tabActiveKey = e;
};
const handleAction = async () => {
if (instanceStore.current?.id) {
const resp = await _deploy(instanceStore.current?.id);
if (resp.status === 200) {
message.success('操作成功!');
instanceStore.refresh(instanceStore.current?.id);
}
}
};
const handleDisconnect = async () => {
if (instanceStore.current?.id) {
const resp = await _disconnect(instanceStore.current?.id);
if (resp.status === 200) {
message.success('操作成功!');
instanceStore.refresh(instanceStore.current?.id);
}
}
};
const handleRefresh = async () => {
if (instanceStore.current?.id) {
await instanceStore.refresh(instanceStore.current?.id);
message.success('操作成功');
}
};
const jumpProduct = () => {
menuStory.jumpPage('device/Product/Detail', {
id: instanceStore.current?.productId,
});
};
onUnmounted(() => {
statusRef.value && statusRef.value.unsubscribe();
});

View File

@ -568,7 +568,17 @@ const query = reactive({
});
const param = ref<Record<string, any>>({
pageSize: 4,
terms: [],
terms: [
{
terms: [
{
column: 'channel',
termType: 'nin',
value: 'plugin',
},
],
},
],
});
const queryParams = ref<Record<string, any>>({});
/**
@ -669,49 +679,6 @@ const driver1 = new Driver({
},
});
/**
* 表格列表
*/
// const columnsMQTT: any[] = [
// {
// title: '',
// dataIndex: 'group',
// key: 'group',
// ellipsis: true,
// width: 100,
// // customCell: (record: any, index: number) => {
// // const list =
// // (config?.routes || []).sort((a: any, b: any) => a - b) || [];
// // const arr = list.filter((res: any) => {
// // // gpsNumber
// // return res?.group == record?.group;
// // });
// // if (index == 0 || list[index - 1]?.group != record?.group) {
// // return { rowSpan: arr.length };
// // } else {
// // return { rowSpan: 0 };
// // }
// // },
// },
// {
// title: 'topic',
// dataIndex: 'topic',
// key: 'topic',
// },
// {
// title: '',
// dataIndex: 'stream',
// key: 'stream',
// ellipsis: true,
// align: 'center',
// width: 100,
// },
// {
// title: '',
// dataIndex: 'description',
// key: 'description',
// },
// ];
let columnsMQTT = ref(<TableColumnType>[]);
const ColumnsMQTT = [
{
@ -760,38 +727,6 @@ const ColumnsHTTP = [
// scopedSlots: { customRender: 'description' },
},
];
// const columnsHTTP: any[] = [
// {
// title: '',
// dataIndex: 'group',
// key: 'group',
// ellipsis: true,
// width: 100,
// // customCell: (record: any, index: number) => {
// // const list =
// // (config?.routes || []).sort((a: any, b: any) => a - b) || [];
// // const arr = list.filter((res: any) => {
// // // gpsNumber
// // return res?.group == record?.group;
// // });
// // if (index == 0 || list[index - 1]?.group != record?.group) {
// // return { rowSpan: arr.length };
// // } else {
// // return { rowSpan: 0 };
// // }
// // },
// },
// {
// title: '',
// dataIndex: 'example',
// key: 'example',
// },
// {
// title: '',
// dataIndex: 'description',
// key: 'description',
// },
// ];
/**
* 获取上下行数据
*/
@ -892,6 +827,28 @@ const submitData = async () => {
accessProvider: current.value?.provider,
messageProtocol: current.value?.protocol,
};
// getConfigView(current.value?.protocol, current.value?.transport).then(
// (resp: any) => {
// metadata.value = (resp?.result[0] as ConfigMetadata) || {
// properties: [],
// };
// // udp
// if (metadata.value?.properties) {
// metadata.value?.properties.forEach((item) => {
// if (
// item.name === '' &&
// (!productStore.current?.configuration ||
// !productStore.current?.configuration.hasOwnProperty(
// item.name,
// ))
// ) {
// formData.data[item.name] =
// item.type.expands?.defaultValue;
// }
// });
// }
// },
// );
const metatdata = JSON.parse(productStore.current?.metadata || '{}');
if (!productStore.current?.metadata) {
const response = await getConfigView(

View File

@ -563,7 +563,7 @@ const query = reactive({
},
},
{
title: '所属部门',
title: '所属组织',
key: 'id$dim-assets',
dataIndex: 'id$dim-assets',
search: {

View File

@ -196,11 +196,12 @@ const resetMetadata = async () => {
// }
const { id } = route.params
if (target === 'device') {
instanceStore.refresh(id as string)
await instanceStore.refresh(id as string)
} else {
productStore.refresh(id as string)
await productStore.getDetail(id as string)
}
metadataStore.set('importMetadata', true)
};
const removeItem = async (record: MetadataItem) => {

View File

@ -61,12 +61,15 @@ const close = () => {
const instanceStore = useInstanceStore()
const productStore = useProductStore()
const metadataMap = {
const metadata = computed(() => {
const metadataMap = {
product: productStore.current?.metadata as string,
device: instanceStore.current?.metadata as string,
};
const metadata = metadataMap[props.type];
const value = ref(metadata)
};
return metadataMap[props.type];
})
// const metadata = metadataMap[props.type];
const value = ref(metadata.value)
const handleExport = async () => {
try {
downloadObject(
@ -86,14 +89,14 @@ const handleConvertMetadata = (key: Key) => {
if (key === 'alink') {
value.value = '';
if (metadata) {
convertMetadata('to', 'alink', JSON.parse(metadata)).then(res => {
convertMetadata('to', 'alink', JSON.parse(metadata.value)).then(res => {
if (res.status === 200) {
value.value = JSON.stringify(res.result)
}
});
}
} else {
value.value = metadata;
value.value = metadata.value;
}
};

View File

@ -237,6 +237,11 @@ const getData = (
return new Promise((resolve) => {
queryFlow(start, end, {
orderBy: 'date',
terms: [{
column : "cardId",
termType: "eq",
value: route.params.id
}]
}).then((resp: any) => {
if (resp.status === 200) {
const sortArray = resp.result.sort(

View File

@ -232,8 +232,8 @@ const handleOk = () => {
btnLoading.value = true;
const resp =
props.type === 'add'
? await add(toRaw(modelRef))
: await edit(toRaw(modelRef));
? await add(toRaw(modelRef)).catch(err => err)
: await edit(toRaw(modelRef)).catch(err => err);
btnLoading.value = false;
if (resp.status === 200) {
message.success('操作成功')

View File

@ -457,6 +457,7 @@ const columns = [
width: 200,
search: {
type: 'string',
defaultTermType: 'eq'
},
},
{
@ -466,6 +467,9 @@ const columns = [
ellipsis: true,
scopedSlots: true,
width: 200,
search: {
type: 'string'
}
},
{
title: '平台对接',
@ -571,7 +575,8 @@ const columns = [
{ label: '正常', value: 'using' },
{ label: '未激活', value: 'toBeActivated' },
{ label: '停机', value: 'deactivate' },
],
{ label: '其它', value: 'using,toBeActivated,deactivate' },
]
},
},
{
@ -737,7 +742,16 @@ const getActions = (
};
const handleSearch = (e: any) => {
params.value = e;
const newParams = (e?.terms as any[])?.map(item1 => {
item1.terms = item1.terms.map((item2: any) => {
if (['cardStateType'].includes(item2.column)) {
item2.termType = 'nin'
}
return item2
})
return item1
})
params.value = { terms: newParams || [] };
};
const onSelectChange = (keys: string[], rows: []) => {

View File

@ -87,7 +87,7 @@
</page-container>
</template>
<script setup lang="ts">
<script setup lang="ts" name='IotCardHome'>
import { getImage } from '@/utils/comm';
import Guide from '../components/Guide.vue';
import moment from 'moment';
@ -113,14 +113,14 @@ const menuHasPermission = useMenuStore().hasPermission;
const btnHasPermission = usePermissionStore().hasPermission;
//
const dashBoardUrl = menuHasPermission('/iot-card/Dashboard');
const platformUrl = menuHasPermission('/iot-card/Platform/Detail');
const recordUrl = menuHasPermission('/iot-card/Record');
const cardUrl = menuHasPermission('/iot-card/CardManagement');
const dashBoardUrl = menuHasPermission('iot-card/Dashboard');
const platformUrl = menuHasPermission('iot-card/Platform/Detail');
const recordUrl = menuHasPermission('iot-card/Record');
const cardUrl = menuHasPermission('iot-card/CardManagement');
//
const paltformPermission = btnHasPermission(`/iot-card/Platform:add`);
const cardPermission = btnHasPermission(`/iot-card/CardManagement:add`);
const paltformPermission = btnHasPermission(`iot-card/Platform:add`);
const cardPermission = btnHasPermission(`iot-card/CardManagement:add`);
const Image = {
1: getImage('/home/1.png'),
@ -179,23 +179,19 @@ const pieChartData = ref<any[]>([
]);
const jumpPage = (data: GuideItemProps) => {
console.log(data.auth)
if (!data.auth){
message.warning('暂无权限,请联系管理员');
return
}
if (data.key === 'EQUIPMENT') {
menuStory.jumpPage(data.url, { id: 'add' });
menuStory.jumpPage(data.url, { id: ':id'});
} else {
menuStory.jumpPage(data.url);
}
};
const jumpDashboard = () => {
// if (dashBoardUrl) {
// router.push(`${dashBoardUrl}`);
// } else {
// message.warning('');
// }
menuStory.jumpPage('iot-card/Dashboard');
};

View File

@ -204,6 +204,7 @@ const rules = {
};
const getDetail = async () => {
console.log(route.params)
if (route.params.id === ':id') return;
const resp: any = await queryById(route.params.id);
if (resp.status === 200) {

View File

@ -238,7 +238,7 @@ const handleSearch = (e: any) => {
* 新增
*/
const handleAdd = () => {
menuStory.jumpPage('iot-card/Platform/Detail', { id: 'add' })
menuStory.jumpPage('iot-card/Platform/Detail', { id: ':id' })
};
</script>

View File

@ -16,8 +16,9 @@
</j-radio-button>
</j-radio-group>
<j-range-picker
format="YYYY-MM-DD"
valueFormat="YYYY-MM-DD"
format="YYYY-MM-DD HH:mm:ss"
valueFormat="YYYY-MM-DD HH:mm:ss"
:show-time="{ format: 'HH:mm:ss' }"
style="margin-left: 12px"
@change="rangeChange"
v-model:value="rangeVal"
@ -28,7 +29,7 @@
</template>
<script setup lang="ts">
import moment from 'moment';
import dayjs from 'dayjs';
import { PropType } from 'vue';
interface BtnOptions {
@ -70,38 +71,37 @@ const rangeVal = ref<[string, string]>();
const rangeChange = (val: any) => {
radioValue.value = undefined;
emit('change', {
start: moment(val[0]).valueOf(),
end: moment(val[1]).valueOf(),
start: dayjs(val[0]).valueOf(),
end: dayjs(val[1]).valueOf(),
type: undefined,
});
};
const getTimeByType = (type: string) => {
const getTimeByType = (type?: string) => {
switch (type) {
case 'hour':
return moment().subtract(1, 'hours').valueOf();
return dayjs().subtract(1, 'hours').valueOf();
case 'week':
return moment().subtract(6, 'days').valueOf();
return dayjs().subtract(6, 'days').valueOf();
case 'month':
return moment().subtract(29, 'days').valueOf();
return dayjs().subtract(29, 'days').valueOf();
case 'year':
return moment().subtract(365, 'days').valueOf();
return dayjs().subtract(365, 'days').valueOf();
default:
return moment().startOf('day').valueOf();
return dayjs().startOf('day').valueOf();
}
};
const handleBtnChange = (val: string) => {
radioValue.value = val;
let endTime = moment(new Date()).valueOf();
const handleBtnChange = (val?: string) => {
let endTime = dayjs(new Date()).valueOf();
let startTime = getTimeByType(val);
if (val === 'yesterday') {
startTime = moment().subtract(1, 'days').startOf('day').valueOf();
endTime = moment().subtract(1, 'days').endOf('day').valueOf();
startTime = dayjs().subtract(1, 'days').startOf('day').valueOf();
endTime = dayjs().subtract(1, 'days').endOf('day').valueOf();
}
rangeVal.value = [
moment(startTime).format('YYYY-MM-DD'),
moment(endTime).format('YYYY-MM-DD'),
dayjs(startTime).format('YYYY-MM-DD HH:mm:ss'),
dayjs(endTime).format('YYYY-MM-DD HH:mm:ss'),
];
emit('change', {
start: startTime,
@ -110,11 +110,8 @@ const handleBtnChange = (val: string) => {
});
};
watch(
() => radioValue.value,
(val) => {
handleBtnChange(val);
},
{ deep: true, immediate: true },
);
nextTick(() => {
handleBtnChange(radioValue.value)
})
</script>

View File

@ -3,7 +3,7 @@
hoverable
:class="[
'card-render',
'container',
`access-${data.type || 'network'}`,
checked === data.id ? 'checked' : '',
]"
@click="checkedChange(data.id)"
@ -106,8 +106,16 @@ const checkedChange = (id: string) => {
}
}
}
.container {
background: url('/public/images/access-icon.png') no-repeat;
.access-media {
background: url('/public/images/access-media.png') no-repeat;
background-position: bottom right;
}
.access-network {
background: url('/public/images/access-network.png') no-repeat;
background-position: bottom right;
}
.access-protocol {
background: url('/public/images/access-protocol.png') no-repeat;
background-position: bottom right;
}
</style>

View File

@ -20,7 +20,11 @@
message: '请输入名称',
trigger: 'blur',
},
{ max: 64, message: '最多可输入64个字符' },
{
max: 64,
message: '最多可输入64个字符',
trigger: 'blur',
},
]"
>
<j-input
@ -119,7 +123,7 @@ const onFinish = async (values: any) => {
};
onMounted(() => {
if (id === ':id') {
if (id !== ':id') {
formState.value = {
name: props.data.name,
description: props.data?.description || '',

View File

@ -1,7 +1,7 @@
<template>
<div>
<j-steps class="steps-steps" :current="stepCurrent">
<j-step v-for="item in steps" :key="item" :title="item" />
<j-step disabled v-for="item in steps" :key="item" :title="item" />
</j-steps>
<div class="steps-content">
<div class="steps-box" v-if="current === 0">
@ -51,6 +51,7 @@
max: 64,
message:
'最多可输入64个字符',
trigger: 'blur',
},
]"
>
@ -75,6 +76,7 @@
max: 64,
message:
'最多可输入64个字符',
trigger: 'blur',
},
]"
>
@ -184,7 +186,11 @@
</PermissionButton>
</div>
<j-scrollbar height="480">
<j-row :gutter="[24, 24]" v-if="procotolList.length > 0">
<j-row
:gutter="[24, 24]"
style="width: 100%"
v-if="procotolList.length > 0"
>
<j-col
:span="8"
v-for="item in procotolList"
@ -193,12 +199,16 @@
<AccessCard
@checkedChange="procotolChange"
:checked="procotolCurrent"
:data="item"
:data="{ ...item, type: 'protocol' }"
>
</AccessCard>
</j-col>
</j-row>
<j-empty v-else description="暂无数据" />
<j-empty
style="margin-top: 10%"
v-else
description="暂无数据"
/>
</j-scrollbar>
</div>
</div>
@ -222,7 +232,11 @@
message: '请输入名称',
trigger: 'blur',
},
{ max: 64, message: '最多可输入64个字符' },
{
max: 64,
message: '最多可输入64个字符',
trigger: 'blur',
},
]"
>
<j-input

View File

@ -1,7 +1,7 @@
<template>
<div>
<j-steps class="steps-steps" :current="stepCurrent">
<j-step v-for="item in steps" :key="item" :title="item" />
<j-step disabled v-for="item in steps" :key="item" :title="item" />
</j-steps>
<div class="steps-content">
<div class="steps-box" v-if="current === 0">
@ -63,6 +63,7 @@
max: 64,
message:
'最多可输入64个字符',
trigger: 'blur',
},
]"
>
@ -86,13 +87,14 @@
max: 64,
message:
'最多可输入64个字符',
trigger: 'blur',
},
]"
>
<template #label>
通知Token
<j-tooltip
title="接收OneNet推送的Token地址"
title="自定义token,可用于验证请求是否来自OneNet"
>
<AIcon
type="QuestionCircleOutlined"
@ -116,6 +118,7 @@
max: 64,
message:
'最多可输入64个字符',
trigger: 'blur',
},
]"
>
@ -266,7 +269,11 @@
</PermissionButton>
</div>
<j-scrollbar height="480">
<j-row :gutter="[24, 24]" v-if="procotolList.length > 0">
<j-row
:gutter="[24, 24]"
style="width: 100%"
v-if="procotolList.length > 0"
>
<j-col
:span="8"
v-for="item in procotolList"
@ -275,12 +282,16 @@
<AccessCard
@checkedChange="procotolChange"
:checked="procotolCurrent"
:data="item"
:data="{ ...item, type: 'protocol' }"
>
</AccessCard>
</j-col>
</j-row>
<j-empty v-else description="暂无数据" />
<j-empty
style="margin-top: 10%"
v-else
description="暂无数据"
/>
</j-scrollbar>
</div>
</div>
@ -305,7 +316,11 @@
message: '请输入名称',
trigger: 'blur',
},
{ max: 64, message: '最多可输入64个字符' },
{
max: 64,
message: '最多可输入64个字符',
trigger: 'blur',
},
]"
>
<j-input

View File

@ -5,7 +5,7 @@
class="steps-steps"
:current="stepCurrent"
>
<j-step v-for="item in steps" :key="item" :title="item" />
<j-step disabled v-for="item in steps" :key="item" :title="item" />
</j-steps>
<div v-if="channel !== 'edge-child-device'" class="steps-content">
<div class="steps-box" v-if="current === 0">
@ -30,7 +30,11 @@
</PermissionButton>
</div>
<j-scrollbar height="480">
<j-row :gutter="[24, 24]" v-if="networkList.length > 0">
<j-row
:gutter="[24, 24]"
style="width: 100%"
v-if="networkList.length > 0"
>
<j-col
:span="8"
v-for="item in networkList"
@ -91,7 +95,11 @@
</AccessCard>
</j-col>
</j-row>
<j-empty v-else description="暂无数据" />
<j-empty
style="margin-top: 10%"
v-else
description="暂无数据"
/>
</j-scrollbar>
</div>
</div>
@ -119,7 +127,11 @@
message: '请输入名称',
trigger: 'blur',
},
{ max: 64, message: '最多可输入64个字符' },
{
max: 64,
message: '最多可输入64个字符',
trigger: 'blur',
},
]"
>
<j-input

View File

@ -1,7 +1,7 @@
<template>
<div style="margin-top: 10px">
<j-steps :current="stepCurrent">
<j-step v-for="item in steps" :key="item" :title="item" />
<j-step disabled v-for="item in steps" :key="item" :title="item" />
</j-steps>
<div class="steps-content">
<div class="steps-box" v-if="current === 0">
@ -29,7 +29,8 @@
},
{
max: 64,
message: '最大可输入64个字符',
message: '最多可输入64个字符',
trigger: 'blur',
},
]"
>
@ -50,7 +51,8 @@
},
{
max: 64,
message: '最大可输入64个字符',
message: '最多可输入64个字符',
trigger: 'blur',
},
]"
>
@ -202,7 +204,11 @@
<j-collapse v-model:activeKey="activeKey">
<j-collapse-panel
:key="cluster.id"
:header="`#${index + 1}.节点`"
:header="
cluster.clusterNodeId
? cluster.clusterNodeId
: `#${index + 1}.配置信息`
"
>
<template #extra>
<span
@ -504,7 +510,7 @@ interface Form2 {
}
interface FormState {
domain: string | undefined;
sipId: string | undefined;
sipId: string | number | undefined;
shareCluster: boolean;
hostPort: {
port: string | undefined;
@ -576,7 +582,7 @@ const removeCluster = (item: Form2) => {
};
const addCluster = () => {
const id = Date.now();
const id: any = Date.now();
dynamicValidateForm.cluster.push({
clusterNodeId: undefined,
port: undefined,
@ -613,7 +619,11 @@ const { resetFields, validate, validateInfos } = useForm(
reactive({
name: [
{ required: true, message: '请输入名称', trigger: 'blur' },
{ max: 64, message: '最多可输入64个字符' },
{
max: 64,
message: '最多可输入64个字符',
trigger: 'blur',
},
],
description: [{ max: 200, message: '最多可输入200个字符' }],
}),
@ -628,7 +638,7 @@ const saveData = () => {
transport: 'SIP',
channel: 'gb28181',
};
params.configuration.sipId = Number(params.configuration?.sipId);
const resp =
id === ':id' ? await save(params) : await update({ ...params, id });
if (resp.status === 200) {
@ -645,7 +655,9 @@ const next = async () => {
data1.hostPort.port = port;
}
if (!data1?.shareCluster) {
let data2 = await formRef2.value?.validate();
await formRef2.value
?.validate()
.then((data2) => {
if (data2 && data2?.cluster) {
data2.cluster.forEach((i: any) => {
i.enabled = true;
@ -656,9 +668,19 @@ const next = async () => {
...data2,
};
}
}
current.value = current.value + 1;
params.configuration = data1;
})
.catch((err) => {
err.errorFields.forEach((item: any) => {
const activeId: any =
dynamicValidateForm.cluster[item.name[1]].id;
if (!activeKey.value.includes(activeId)) {
activeKey.value.push(activeId); //
}
});
});
}
};
const prev = () => {
current.value = current.value - 1;
@ -698,11 +720,25 @@ onMounted(() => {
});
if (id !== ':id') {
formState.value = props.data.configuration;
formData.value = {
name: props.data.name,
description: props.data?.description || '',
const { configuration, name, description = '' } = props.data;
formData.value = { name, description };
if (configuration?.shareCluster) {
formState.value = {
...formState.value,
...props.data.configuration,
};
} else {
formState.value = {
...formState.value,
...props.data.configuration,
};
dynamicValidateForm.cluster = configuration.cluster;
if (dynamicValidateForm.cluster.length === 1) {
activeKey.value = ['1'];
dynamicValidateForm.cluster[0].id = 1;
}
}
}
});

View File

@ -21,7 +21,11 @@
message: '请输入名称',
trigger: 'blur',
},
{ max: 64, message: '最多可输入64个字符' },
{
max: 64,
message: '最多可输入64个字符',
trigger: 'blur',
},
]"
>
<j-input

View File

@ -1,7 +1,7 @@
<template>
<div>
<j-steps :current="stepCurrent">
<j-step v-for="item in steps" :key="item" :title="item" />
<j-step disabled v-for="item in steps" :key="item" :title="item" />
</j-steps>
<div class="steps-content">
<div class="steps-box" v-if="current === 0">
@ -26,7 +26,11 @@
</PermissionButton>
</div>
<j-scrollbar height="480">
<j-row :gutter="[24, 24]" v-if="networkList.length > 0">
<j-row
:gutter="[24, 24]"
style="width: 100%"
v-if="networkList.length > 0"
>
<j-col
:span="8"
v-for="item in networkList"
@ -40,6 +44,7 @@
description: item.description
? item.description
: descriptionList[provider.id],
type: 'network',
}"
>
<template #other>
@ -87,7 +92,11 @@
</AccessCard>
</j-col>
</j-row>
<j-empty v-else description="暂无数据" />
<j-empty
style="margin-top: 10%"
v-else
description="暂无数据"
/>
</j-scrollbar>
</div>
<div class="steps-box" v-else-if="current === 1">
@ -112,21 +121,29 @@
</PermissionButton>
</div>
<j-scrollbar height="480">
<j-row :gutter="[24, 24]" v-if="procotolList.length > 0">
<j-row
:gutter="[24, 24]"
style="width: 100%"
v-if="procotolList.length > 0"
>
<j-col
:span="8"
v-for="item in procotolList"
:key="item?.id"
>
<access-card
<AccessCard
@checkedChange="procotolChange"
:checked="procotolCurrent"
:data="item"
:data="{ ...item, type: 'protocol' }"
>
</access-card>
</AccessCard>
</j-col>
</j-row>
<j-empty v-else description="暂无数据" />
<j-empty
style="margin-top: 10%"
v-else
description="暂无数据"
/>
</j-scrollbar>
</div>
<div class="steps-box" v-else>
@ -366,7 +383,11 @@ const { resetFields, validate, validateInfos } = useForm(
reactive({
name: [
{ required: true, message: '请输入名称', trigger: 'blur' },
{ max: 64, message: '最多可输入64个字符' },
{
max: 64,
message: '最多可输入64个字符',
trigger: 'blur',
},
],
description: [{ max: 200, message: '最多可输入200个字符' }],
}),

View File

@ -186,6 +186,7 @@ const tableRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({});
let providersList = ref<Record<string, any>>([]);
const providersOptions = ref<Record<string, any>>([]);
const statusMap = new Map();
statusMap.set('enabled', 'success');
@ -207,13 +208,7 @@ const columns = [
key: 'provider',
search: {
type: 'select',
options: async () => {
const res: any = await getProviders();
return (res?.result || [])?.map((item: any) => ({
lable: item.name,
value: item.id,
}));
},
options: providersOptions,
},
},
{
@ -315,6 +310,10 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
const getProvidersList = async () => {
const res: any = await getProviders();
providersList.value = res.result;
providersOptions.value = (res?.result || [])?.map((item: any) => ({
label: item.name,
value: item.id,
}));
};
getProvidersList();

View File

@ -54,12 +54,17 @@ const loading = ref(false);
const handleChange = (info: UploadChangeParam) => {
loading.value = true;
if (info.file.status === 'done') {
onlyMessage('上传成功!', 'success');
const result = info.file.response?.result;
const reg = new RegExp(/\.pem$/i);
if (reg.test(info.file.name)) {
keystoreBase64.value = result;
loading.value = false;
emit('change', result);
emit('update:modelValue', result);
onlyMessage('上传成功!', 'success');
} else {
onlyMessage('请上传.pem格式的文件', 'error');
}
loading.value = false;
}
};
const textChange = (val: any) => {

View File

@ -9,7 +9,7 @@
:model="formData"
name="basic"
:label-col="{ span: 8 }"
:wrapper-col="{ span: 16 }"
:wrapper-col="{ span: 24 }"
autocomplete="off"
>
<j-form-item
@ -86,7 +86,7 @@
<h1>2. 配置说明</h1>
<h2>1证书文件</h2>
<div>
您可以使用文本编辑工具打开PEM或者CRT格式的证书文件复制其中的内容并粘贴到该文本框或者单击该文本框下的上传并选择存储在本地计算机的证书文件将文件内容上传到文本框
您可以使用文本编辑工具打开PEM格式的证书文件复制其中的内容并粘贴到该文本框或者单击该文本框下的上传并选择存储在本地计算机的证书文件将文件内容上传到文本框
</div>
<h2>2证书私钥</h2>
<div>
@ -208,31 +208,4 @@ detail(id);
}
}
}
.doc {
height: 100%;
padding: 0 24px;
overflow-y: auto;
color: rgba(#000, 0.8);
font-size: 14px;
background-color: #fff;
h1 {
margin: 16px 0;
color: rgba(#000, 0.85);
font-weight: bold;
font-size: 14px;
&:first-child {
margin-top: 0;
}
}
h2 {
margin: 6px 10px;
color: rgba(0, 0, 0, 0.8);
font-weight: 400;
font-size: 14px;
}
}
</style>

View File

@ -76,14 +76,17 @@ const columns = [
width: 200,
ellipsis: true,
search: {
type: 'select',
options: [
{
label: '证书标准',
value: 'common',
},
],
type: 'string',
},
// search: {
// type: 'select',
// options: [
// {
// label: '',
// value: 'common',
// },
// ],
// },
scopedSlots: true,
},
{
@ -118,17 +121,17 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
return [];
}
return [
{
key: 'view',
text: '查看',
tooltip: {
title: '查看',
},
icon: 'EyeOutlined',
onClick: async () => {
handlEye(data.id);
},
},
// {
// key: 'view',
// text: '',
// tooltip: {
// title: '',
// },
// icon: 'EyeOutlined',
// onClick: async () => {
// handlEye(data.id);
// },
// },
{
key: 'update',
text: '编辑',
@ -143,6 +146,9 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
{
key: 'delete',
text: '删除',
tooltip: {
title: '删除',
},
popConfirm: {
title: '确认删除?',
okText: ' 确定',

View File

@ -91,17 +91,41 @@
>
<j-collapse
v-model:activeKey="activeKey"
class="collapse"
:class="[
!formData.shareCluster
? 'collapse'
: 'collapse-panel',
]"
:ghost="formData.shareCluster"
collapsible="header"
>
<j-collapse-panel
:key="cluster.id"
:show-arrow="!formData.shareCluster"
>
<!-- <j-collapse-panel
:key="cluster.id"
:header="
cluster.serverId
? cluster.serverId
: `#${index + 1}.配置信息`
: !formData.shareCluster
? `#${index + 1}.配置信息`
: ''
"
collapsible="header"
>
:show-arrow="!formData.shareCluster"
> -->
<template #header v-if="!shareCluster">
<div class="collapse-header">
{{
cluster.serverId
? cluster.serverId
: !formData.shareCluster
? `#${index + 1}.配置信息`
: ''
}}
</div>
</template>
<template #extra v-if="!shareCluster">
<j-popconfirm
@confirm.prevent="
@ -1112,6 +1136,10 @@ const filterConfigByType = (data: any, type: string) => {
});
};
const changeheader = (value: string) => {
console.log(22, value);
};
const getPortOptions = (portOptions: object, index = 0) => {
if (!portOptions) return;
const type = formData.value.type;
@ -1358,7 +1386,7 @@ watch(
top: -10px;
left: 10px;
right: 10px;
width: calc(100%-10px);
width: calc(100% - 10px);
height: 100%;
background-color: #f4f4f4;
content: ' ';
@ -1369,7 +1397,20 @@ watch(
.collapse {
margin-bottom: 20px;
background: #f4f4f4;
:deep(.ant-collapse-header-text) {
flex: 1;
}
}
.collapse-panel {
margin-bottom: 20px;
border: #d9d9d9 1px solid;
background: #f4f4f4;
border-radius: 2px;
:deep(.ant-collapse-header) {
padding: 0;
}
}
.delete-btn {
display: inline-block;
color: #e50012;

View File

@ -20,8 +20,8 @@ export const Configuration = {
script: '',
size: '',
length: '4',
offset: '0',
little: 'false',
offset: undefined,
little: undefined,
},
};
@ -103,11 +103,12 @@ export const isVisible = (
) => VisibleData[LastName].includes(dependencies);
export const Validator = {
regIp: new RegExp(
/((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}/,
regIpv4: new RegExp(
/^((([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))\.){3}(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))$/,
),
regIPv6: new RegExp(/^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/),
regDomain: new RegExp(
/[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?/,
/^https?:\/\/(([a-zA-Z0-9_-])+(\.)?)*(:\d+)?(\/((\.)?(\?)?=?&?[a-zA-Z0-9_-](\?)?)*)*$/i,
),
regOnlyNumber: new RegExp(/^\d+$/),
};
@ -159,7 +160,8 @@ export const Rules = {
message: '请输入公网地址',
},
{
pattern: Validator.regIp || Validator.regDomain,
pattern:
Validator.regIpv4 || Validator.regIPv6 || Validator.regDomain,
message: '请输入正确格式的域名或ip',
},
],
@ -179,7 +181,9 @@ export const Rules = {
message: '请输入远程地址',
},
{
pattern: Validator.regIp || Validator.regDomain,
pattern:
Validator.regIpv4 || Validator.regIPv6 || Validator.regDomain,
message: '请输入正确格式的域名或ip',
},
],

View File

@ -76,7 +76,10 @@
message: '请输入API Host',
},
{
pattern: regDomain,
pattern:
Validator.regIpv4 ||
Validator.regIPv6 ||
Validator.regDomain,
message: '请输入正确的IP地址或者域名',
},
]"
@ -130,7 +133,10 @@
message: '请输入RTP IP',
},
{
pattern: regDomain,
pattern:
Validator.regIpv4 ||
Validator.regIPv6 ||
Validator.regDomain,
message: '请输入正确的IP地址或者域名',
},
]"
@ -191,7 +197,15 @@
style="width: 100%"
:min="1"
:max="
formData.configuration.dynamicRtpPortRange1
Number(
formData.configuration
.dynamicRtpPortRange1,
) < 65535
? Number(
formData.configuration
.dynamicRtpPortRange1,
)
: 65535
"
:precision="0"
placeholder="起始端口"
@ -279,8 +293,17 @@ const formRef = ref<FormInstance>();
const loading = ref(false);
const options = ref([]);
const checked = ref(false);
const regDomain =
/[j-zA-Z0-9][-j-zA-Z0-9]{0,62}(\.[j-zA-Z0-9][-j-zA-Z0-9]{0,62})+\.?/;
const Validator = {
regIpv4: new RegExp(
/^((([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))\.){3}(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))$/,
),
regIPv6: new RegExp(/^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/),
regDomain: new RegExp(
/^https?:\/\/(([a-zA-Z0-9_-])+(\.)?)*(:\d+)?(\/((\.)?(\?)?=?&?[a-zA-Z0-9_-](\?)?)*)*$/i,
),
regOnlyNumber: new RegExp(/^\d+$/),
};
const formData = ref<FormDataType>({
name: '',

View File

@ -38,7 +38,7 @@
:statusText="slotProps.state?.text"
@click="openRuleEditor"
:statusNames="{
started: 'success',
started: 'processing',
disable: 'error',
}"
>
@ -88,17 +88,17 @@
</CardBox>
</template>
<template #state="slotProps">
<j-badge
<BadgeStatus
:text="
slotProps.state?.value === 'started'
? '正常'
: '禁用'
"
:status="
slotProps.state?.value === 'started'
? 'success'
: 'error'
"
:status="slotProps.state?.value"
:statusNames="{
started: 'processing',
disable: 'error',
}"
/>
</template>
<template #action="slotProps">

View File

@ -3,7 +3,7 @@
title='触发规则'
visible
:width='820'
@click='save'
@ok='save'
@cancel='cancel'
:maskClosable="false"
>

View File

@ -3,7 +3,7 @@
:columns="columns"
type='simple'
@search="handleSearch"
class='search'
class="scene-search"
target="scene-triggrt-device-device"
/>
<j-divider style='margin: 0' />

View File

@ -22,11 +22,16 @@
<j-form-item>定时调用所选功能</j-form-item>
</j-col>
<j-col :span='24'>
<j-form-item
name='functionData'
:rules="rules"
>
<FunctionCall
:value='_value'
v-model:value='formModel.functionData'
:data='functionData'
@change='callDataChange'
/>
</j-form-item>
</j-col>
</j-row>
</j-form>
@ -66,9 +71,9 @@ const props = defineProps({
const emit = defineEmits<Emit>()
const invokeForm = ref()
const formModel = reactive({
functionId: props.functionId
functionId: props.functionId,
functionData: props.functionParameters
})
const _value = ref<any[]>(props.functionParameters)
/**
* 获取当前选择功能属性
@ -94,13 +99,29 @@ const functionData = computed(() => {
return arrCache
})
const rules = [{
validator(_: string, value: any) {
if (!value?.length && functionData.value.length) {
return Promise.reject('请输入功能值')
} else {
let hasValue = value.find((item: { name: string, value: any}) => !item.value)
if (hasValue) {
const functionItem = functionData.value.find((item: any) => item.id === hasValue.name)
return Promise.reject(functionItem?.name ? `请输入${functionItem?.name}` : '请输入功能值')
}
}
return Promise.resolve();
}
}]
const onSelect = (v: string, item: any) => {
formModel.functionData = []
emit('update:action', `执行${item.name}`)
emit('update:functionId', v)
emit('update:functionParameters', [])
}
const callDataChange = (v: any[]) => {
_value.value = v
emit('update:functionParameters', v)
}

View File

@ -3,7 +3,7 @@
:columns="columns"
type='simple'
@search="handleSearch"
class='search'
class="scene-search"
target="scene-triggrt-device-device"
/>
<j-divider style='margin: 0' />

View File

@ -1,5 +1,5 @@
<template>
<div class='actions-branches-item'>
<div class='manual actions-branches-item'>
<j-form-item
:rules="actionRules"
:name="['branches', 0, 'then']"
@ -32,6 +32,21 @@ const actionRules = [{
</script>
<style scoped>
<style scoped lang='less'>
@minWidth: 75%;
.manual {
&.actions-branches-item {
width: 100%;
}
}
@media (min-width: 1600px) {
.manual {
&.actions-branches-item {
width: @minWidth;
}
}
}
</style>

View File

@ -3,7 +3,7 @@
title='触发规则'
visible
:width='820'
@click='save'
@ok='save'
@cancel='cancel'
>
<Timer
@ -15,17 +15,16 @@
<script setup lang="ts" name="timerAddModel">
import Timer from '../components/Timer'
import type { OperationTimer } from '@/views/rule-engine/Scene/typings'
import {nextTick, PropType} from "vue";
import {TriggerDevice} from "@/views/rule-engine/Scene/typings";
import { PropType} from "vue";
import {handleTimerOptions} from "@/views/rule-engine/Scene/Save/components/Timer/util";
type Emit = {
(e: 'cancel'): void
(e: 'save', data: TriggerDevice, options: Record<string, any>): void
(e: 'save', data: OperationTimer, options: Record<string, any>): void
}
const props = defineProps({
timer: {
value: {
type: Object as PropType<OperationTimer>,
default: () => ({})
}
@ -39,14 +38,14 @@ interface AddModelType {
}
const addModel = reactive<AddModelType>({
timer: props.timer
timer: props.value
})
const save = async () => {
const timerData = await timerRef.value?.validateFields()
if (timerData) {
const options = handleTimerOptions(timerData)
emit("save", timerData, options)
const options = handleTimerOptions(addModel.timer)
emit("save", addModel.timer, options)
}
}
@ -54,9 +53,9 @@ const cancel = () => {
emit("cancel")
}
nextTick(() => {
Object.assign(addModel, props.timer)
})
// watchEffect(() => {
// addModel.timer = props.value
// })
</script>

View File

@ -0,0 +1,58 @@
<template>
<div :class='["trigger-options-content", isAdd ? "is-add" : ""]'>
<span v-if='!isAdd'> 点击配置定时触发 </span>
<template v-else>
<div class='center-item'>
<AIcon v-if='options.selectorIcon' :type='options.selectorIcon' class='icon-padding-right' />
<span class='trigger-options-name'>
<Ellipsis style='max-width: 310px'>
{{ options.name }}
</Ellipsis>
</span>
<span v-if='options.extraName'>{{ options.extraName }}</span>
</div>
<div v-if='options.when'>
<span className='trigger-options-when'>{{ options.when }}</span>
</div>
<div v-if='options.time'>
<span className='trigger-options-time'>{{ options.time }}</span>
</div>
<div v-if='options.extraTime'>
<span className='trigger-options-extraTime'>{{ options.extraTime }}</span>
</div>
</template>
</div>
</template>
<script setup lang='ts' name='TimerTitle'>
const props = defineProps({
options: {
type: Object,
default: () => ({})
}
})
const isAdd = computed(() => {
console.log(props.options, Object.keys(props.options).length)
return !!Object.keys(props.options).length
})
</script>
<style scoped lang='less'>
.trigger-options-content {
display: inline-flex;
gap: 16px;
.center-item {
display: flex;
align-items: center;
}
.icon-padding-right {
padding-right: 4px;
}
}
</style>

View File

@ -2,7 +2,7 @@
<div class='timer'>
<j-form-item
:rules="rules"
name="timer"
:name="['trigger', 'timer']"
>
<template #label>
<TitleComponent data='触发规则' style='font-size: 14px;' />
@ -29,8 +29,7 @@
v-if="visible"
@cancel='visible = false'
@save="save"
:value="data.trigger.device"
:options="data.options.trigger"
:value="data.trigger.timer"
/>
</div>
</template>
@ -41,6 +40,7 @@ import { storeToRefs } from 'pinia';
import Action from '../action/index.vue';
import AddModel from './AddModal.vue'
import AddButton from '../components/AddButton.vue'
import Title from './Title.vue'
import type { OperationTimer, BranchesThen } from '@/views/rule-engine/Scene/typings'
const sceneStore = useSceneStore();
@ -49,6 +49,7 @@ const visible = ref(false)
const rules = [{
validator(_: any, v: any) {
console.log(v)
if (!v) {
return Promise.reject(new Error('请配置定时触发规则'));
}
@ -85,5 +86,21 @@ const save = (_data: OperationTimer, options: Record<string, any>) => {
}
</script>
<style scoped>
<style scoped lang='less'>
@minWidth: 75%;
.timer {
.actions-branches-item {
width: 100%;
}
}
@media (min-width: 1600px) {
.timer {
.actions-branches-item {
width: @minWidth;
}
}
}
</style>

View File

@ -3,7 +3,7 @@
:columns="columns"
type="simple"
@search="handleSearch"
class="search"
class="scene-search"
target="scene-trigger-device-product"
/>
<j-divider style="margin: 0" />

View File

@ -22,6 +22,7 @@
showSearch
placeholder="请选择功能"
v-model:value="modelRef.message.functionId"
@select='functionSelect'
>
<j-select-option
v-for="item in metadata?.functions || []"
@ -34,7 +35,7 @@
<j-form-item
v-if="modelRef.message.functionId"
:name="['message', 'inputs']"
:rules="[{ required: true, message: '请输入功能值' }]"
:rules="functionRules"
>
<EditTable
:functions="functions"
@ -138,6 +139,25 @@ const modelRef = reactive({
},
});
const functionSelect = () => {
modelRef.message.inputs = []
}
const functionRules = [{
validator(_: string, value: any) {
if (!value?.length && functions.value.length) {
return Promise.reject('请输入功能值')
} else {
const hasValue = value.find((item: { name: string, value: any}) => !item.value)
if (hasValue) {
const functionItem = functions.value.find((item: any) => item.id === hasValue.name)
return Promise.reject(functionItem?.name ? `请输入${functionItem.name}` : '请输入功能值')
}
}
return Promise.resolve();
}
}]
const metadata = ref<{
functions: any[];
properties: any[];

View File

@ -3,7 +3,7 @@
:columns="columns"
type="simple"
@search="handleSearch"
class="search"
class="scene-search"
target="scene-trigger-device-device"
/>
<j-divider style="margin: 0" />

View File

@ -101,6 +101,7 @@ const valueChange = debounce(() => {
const _value = dataSource.value.map(item => ({
name: item.id, value: item.value
}))
console.log(_value)
emit('change', _value)
emit('update:value', _value)
}, 500)

View File

@ -17,6 +17,7 @@ import { numberToString } from './util'
type Emit = {
(e: 'update:value', data: Array<number>):void
(e: 'change', data: Array<number>):void
}
const props = defineProps({
@ -48,6 +49,7 @@ const change = (number: number) => {
}
rowKeys.value = [..._keys.values()]
emit('update:value', rowKeys.value)
emit('change', rowKeys.value)
}
const allActive = computed(() => {
@ -57,7 +59,9 @@ const allActive = computed(() => {
watch(() => props.type, () => {
const isMonth = props.type === 'month'
const day = isMonth ? 31 : 7
if (!props.value.length) {
change(0)
}
timeOptions.value = new Array(day)
.fill(1)
.map((_, index) => {

View File

@ -15,6 +15,7 @@
]'
option-type='button'
button-style='solid'
@change='updateValue'
/>
</j-form-item>
<j-form-item v-if='showCron' name='cron' :rules="[
@ -32,11 +33,11 @@
}
}
]">
<j-input placeholder='corn表达式' v-model:value='formModel.cron' />
<j-input placeholder='corn表达式' v-model:value='formModel.cron' @change='updateValue' />
</j-form-item>
<template v-else>
<j-form-item name='when'>
<WhenOption v-model:value='formModel.when' :type='formModel.trigger' />
<WhenOption v-model:value='formModel.when' :type='formModel.trigger' @change='updateValue' />
</j-form-item>
<j-form-item name='mod'>
<j-radio-group
@ -47,13 +48,19 @@
]'
option-type='button'
button-style='solid'
@change='updateValue'
/>
</j-form-item>
</template>
<j-space v-if='showOnce' style='display: flex;gap: 24px'>
<j-form-item :name="['once', 'time']">
<j-time-picker valueFormat='HH:mm:ss' v-model:value='formModel.once.time' style='width: 100%'
format='HH:mm:ss' />
<j-time-picker
valueFormat='HH:mm:ss'
v-model:value='formModel.once.time'
style='width: 100%'
format='HH:mm:ss'
@change='updateValue'
/>
</j-form-item>
<j-form-item> 执行一次</j-form-item>
</j-space>
@ -68,6 +75,7 @@
@change='(v) => {
formModel.period.from = v[0]
formModel.period.to = v[1]
updateValue()
}'
/>
</j-form-item>
@ -83,6 +91,7 @@
:min='1'
:max='59'
v-model:value='formModel.period.every'
@change='updateValue'
>
<template #addonAfter>
<j-select
@ -92,6 +101,7 @@
{ label: "分", value: "minutes" },
{ label: "小时", value: "hours" },
]'
@select='updateValue'
/>
</template>
</j-input-number>
@ -103,7 +113,7 @@
<script setup lang='ts' name='Timer'>
import type { PropType } from 'vue'
import moment from 'moment'
import dayjs from 'dayjs'
import WhenOption from './WhenOption.vue'
import { cloneDeep } from 'lodash-es'
import type { OperationTimer } from '../../../typings'
@ -131,23 +141,21 @@ const emit = defineEmits<Emit>()
const formModel = reactive<OperationTimer>({
trigger: 'week',
when: [],
when: props.value.when || [],
mod: 'period',
cron: undefined,
once: {
time: moment(new Date()).format('HH:mm:ss')
time: dayjs(new Date()).format('HH:mm:ss')
},
period: {
from: moment(new Date()).startOf('day').format('HH:mm:ss'),
to: moment(new Date()).endOf('day').format('HH:mm:ss'),
from: dayjs(new Date()).startOf('day').format('HH:mm:ss'),
to: dayjs(new Date()).endOf('day').format('HH:mm:ss'),
every: 1,
unit: 'seconds'
}
})
const timerForm = ref()
Object.assign(formModel, props.value)
const showCron = computed(() => {
return formModel.trigger === 'cron'
})
@ -160,7 +168,7 @@ const showPeriod = computed(() => {
return formModel.trigger !== 'cron' && formModel.mod === 'period'
})
watch(() => formModel, () => {
const updateValue = () => {
const cloneValue = cloneDeep(formModel)
if (cloneValue.trigger === 'cron') {
delete cloneValue.when
@ -174,7 +182,7 @@ watch(() => formModel, () => {
delete cloneValue.period
}
emit('update:value', cloneValue)
}, { deep: true })
}
defineExpose({
validateFields: () => new Promise(async (resolve) => {
@ -182,6 +190,8 @@ defineExpose({
resolve(data)
})
})
Object.assign(formModel, props.value)
formModel.when = props.value.when || []
</script>

View File

@ -57,15 +57,17 @@ const classNames = computed(() => {
})
const handleClick = (type: string) => {
if (!props.disabled) {
emit('update:modelValue', type)
}
}
</script>
<style scoped lang='less'>
// @import 'ant-design-vue/es/style/themes/default.less';
.scene-trigger-way-warp {display: flex;
.scene-trigger-way-warp {
display: flex;
flex-wrap: wrap;
gap: 16px 24px;
width: 100%;
@ -115,5 +117,16 @@ const handleClick = (type: string) => {
}
}
}
&.disabled {
color: rgba(#000, .8);
.way-item-image {
opacity: 0.6;
}
.trigger-way-item {
cursor: not-allowed;
}
}
}
</style>

View File

@ -115,3 +115,6 @@ onUnmounted(() => {
}
}
</style>
<style lang='less'>
@import "./style.less";
</style>

View File

@ -0,0 +1,3 @@
.scene-search {
padding: 24px 0 0 0;
}

View File

@ -300,7 +300,7 @@ const getActions = (
];
if (data.triggerType === 'manual') {
const _item: ActionsType = {
key: 'trigger',
key: 'tigger',
text: '手动触发',
disabled: data.state?.value === 'disable',
tooltip: {

View File

@ -70,7 +70,7 @@
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
online: 'success',
online: 'processing',
offline: 'error',
notActive: 'warning',
}"
@ -147,7 +147,7 @@
:status="slotProps.state.value"
:text="slotProps.state.text"
:statusNames="{
online: 'success',
online: 'processing',
offline: 'error',
notActive: 'warning',
}"
@ -247,6 +247,9 @@ const columns = [
search: {
rename: 'productId$product-info',
type: 'select',
handleValue(value: string) {
return `id is ${value}`
},
options: () =>
new Promise((resolve) => {
const params = {
@ -288,14 +291,9 @@ const columns = [
search: {
type: 'select',
options: [
{
label: '正常',
value: 1,
},
{
label: '禁用',
value: 0,
},
{ label: '禁用', value: 'notActive' },
{ label: '离线', value: 'offline' },
{ label: '在线', value: 'online' },
],
},
scopedSlots: true,

View File

@ -68,9 +68,8 @@
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
online: 'success',
offline: 'error',
notActive: 'warning',
1: 'processing',
0: 'error',
}"
>
<template #img>
@ -172,9 +171,8 @@
:status="slotProps.state.value"
:text="slotProps.state.text"
:statusNames="{
online: 'success',
offline: 'error',
notActive: 'warning',
1: 'processing',
0: 'error',
}"
></BadgeStatus>
</template>

View File

@ -4,7 +4,7 @@
<pro-search
:columns="columns"
target="category"
@search="(params:any)=>queryParams = {...params}"
@search="handleParams"
/>
<j-pro-table
@ -262,6 +262,23 @@ type dictType = {
name: string;
};
type modalType = '' | 'add' | 'edit' | 'reset';
const handleParams = (params: any)=> {
const newParams = (params?.terms as any[])?.map(item1 => {
item1.terms = item1.terms.map((item2: any) => {
if (['telephone', 'email'].includes(item2.column)) {
return {
column: 'id$user-detail',
value: [item2]
}
}
return item2
})
return item1
})
queryParams.value = { terms: newParams || [] }
}
</script>
<style lang="less" scoped>

View File

@ -92,8 +92,8 @@ export default defineConfig(({ mode}) => {
[env.VITE_APP_BASE_API]: {
// target: 'http://192.168.33.22:8800',
// target: 'http://192.168.32.244:8881',
// target: 'http://120.77.179.54:8844', // 120测试
target: 'http://192.168.33.46:8844', // 本地开发环境
target: 'http://120.77.179.54:8844', // 120测试
// target: 'http://192.168.33.46:8844', // 本地开发环境
ws: 'ws://192.168.33.46:8844',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')

View File

@ -3700,8 +3700,8 @@ jetlinks-store@^0.0.3:
jetlinks-ui-components@^1.0.5:
version "1.0.5"
resolved "http://47.108.170.157:9013/jetlinks-ui-components/-/jetlinks-ui-components-1.0.5.tgz#682711e0f69c141fff2c256db61a060c82539611"
integrity sha512-rQxD/YlE+XSAG7BWIcFTtKrCQJXk5o+TUgejyuUT/baBThJB6xYt1k2dQEdXyiwpukYen5FzaoLpelSD9SUegw==
resolved "http://47.108.170.157:9013/jetlinks-ui-components/-/jetlinks-ui-components-1.0.5.tgz#77bd58156212d793fdebe41fc8864eeed5db7182"
integrity sha512-VXuCMJlMV/bbmBhPtBzY/BUBIvGebQbguLPE06xps79i5Pjq46+HBW8VPsJ9MwvyMhYkhzPlQJu9Ft7v5uo+yA==
dependencies:
"@vueuse/core" "^9.12.0"
ant-design-vue "^3.2.15"