Merge remote-tracking branch 'origin/dev' into dev

# Conflicts:
#	src/views/rule-engine/Scene/Save/Device/index.vue
#	yarn.lock
This commit is contained in:
xieyonghong 2023-03-02 21:48:50 +08:00
commit 376cfe4d3d
96 changed files with 9737 additions and 764 deletions

6368
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -36,12 +36,15 @@
"pinia": "^2.0.28",
"unplugin-auto-import": "^0.12.1",
"unplugin-vue-components": "^0.22.12",
"v-clipboard3": "^0.1.4",
"vite-plugin-monaco-editor": "^1.1.0",
"vue": "^3.2.45",
"vue-json-viewer": "^3.0.4",
"vue-router": "^4.1.6",
"vue3-json-viewer": "^2.2.2",
"vue3-markdown-it": "^1.0.10",
"vue3-ts-jsoneditor": "^2.7.1"
"vue3-ts-jsoneditor": "^2.7.1",
"vue3-video-play": "^1.3.1-beta.6"
},
"devDependencies": {
"@commitlint/cli": "^17.4.1",

View File

@ -0,0 +1,22 @@
import server from '@/utils/request'
// 获取记录列表
export const getList_api = (data:object): any =>server.get(`/notifications/_query`,encodeParams(data))
// 修改记录状态
export const changeStatus_api = (type:'_read'|'_unread',data:string[]): any =>server.post(`/notifications/${type}`,data)
const encodeParams = (params: Record<string, any>) => {
let result = {}
for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
const value = params[key];
if(key === 'terms') {
result['terms[0].column:'] = 0
result['terms[0].value'] = JSON.stringify(value[0])
}else result[key] = value
}
}
return result
};

View File

@ -0,0 +1,22 @@
import server from '@/utils/request'
// 获取通知订阅列表
export const getNoticeList_api = () => server.post(`/notifications/subscriptions/_query/`);
// 保存通知订阅
export const save_api = (data:any) => server.patch(`/notifications/subscribe`, data);
// 保存通知订阅
export const remove_api = (id:string) => server.remove(`/notifications/subscription/${id}`);
/**
*
* @param id id
* @param status
*/
export const changeStatus_api = (id: string, status: '_disabled' | '_enabled') => server.put(`/notifications/subscription/${id}/${status}`);
// 获取订阅类型
export const getTypeList_api = () => server.get(`/notifications/providers`);
// 获取告警规则列表
export const getAlarmList_api = () => server.post(`/alarm/config/_query/no-paging`, {
sorts: [{ name: 'createTime', order: 'desc' }],
paging: false,
});

View File

@ -1,4 +1,6 @@
import server from '@/utils/request'
import server from '@/utils/request';
import { LocalStore } from '@/utils/comm';
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
export default {
// 列表
@ -12,5 +14,54 @@ export default {
// 修改
update: (id: string, data: any) => server.put(`/media/channel/${id}`, data),
// 删除
del: (id: string) => server.remove(`media/channel/${id}`),
del: (id: string) => server.remove(`/media/channel/${id}`),
// ========== 视频播放 ==========
// 开始直播
ptzStart: (deviceId: string, channelId: string, type: string) =>
`${BASE_API_PATH}/media/device/${deviceId}/${channelId}/live.${type}?:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`,
// 云台控制-停止
ptzStop: (deviceId: string, channelId: string) => server.post(`/media/device/${deviceId}/${channelId}/_ptz/STOP`),
// 云台控制-缩放、转向等
ptzTool: (deviceId: string, channelId: string, direct: string, speed: number = 90) =>
server.post(`/media/device/${deviceId}/${channelId}/_ptz/${direct}/${speed}`),
// 重置
mediaStop: (deviceId: string, channelId: string) => server.post(`/media/device/${deviceId}/${channelId}/_stop`),
// 查询是否正在录像
ptzIsRecord: (deviceId: string, channelId: string) => server.get(`/media/device/${deviceId}/${channelId}/live/recording`),
// 开始录像
recordStart: (deviceId: string, channelId: string, data: any) =>
server.post(`/media/device/${deviceId}/${channelId}/_record`, data),
// 停止录像
recordStop: (deviceId: string, channelId: string, data: any) =>
server.post(`/media/device/${deviceId}/${channelId}/_stop-record`, data),
// 查询本地回放记录
queryRecordLocal: (deviceId: string, channelId: string, data: any) =>
server.post(`/media/device/${deviceId}/${channelId}/records/in-local`, data),
// 播放本地回放
playbackLocal: (deviceId: string, channelId: string, suffix: string) =>
server.get(`/media/device/${deviceId}/${channelId}/playback.${suffix}`),
// 本地录像播放控制
playbackControl: (deviceId: string, channelId: string) =>
server.post(`/media/device/${deviceId}/${channelId}/stream-control`),
// 查询云端回放记录
recordsInServer: (deviceId: string, channelId: string) =>
server.post(`/media/device/${deviceId}/${channelId}/records/in-server`),
// 查询云端回放文件信息
recordsInServerFiles: (deviceId: string, channelId: string) =>
server.post(`/media/device/${deviceId}/${channelId}/records/in-server/files`),
// 播放云端回放
playbackStart: (recordId: string) => server.get(`/media/record/${recordId}.mp4`),
}

View File

@ -34,5 +34,9 @@ export default {
// 微信绑定用户
weChatBindUser: (data: any, id: string) => patch(`/user/third-party/weixin_corpMessage/${id}`, data),
// 解绑
unBindUser: (data: any, id: string) => post(`/user/third-party/${id}/_unbind`, data)
unBindUser: (data: any, id: string) => post(`/user/third-party/${id}/_unbind`, data),
//通知类型
queryMessageType: () => get(`/notifier/config/types`),
// 不分页-列表
queryListNoPaging: (data: any) => post(`/notifier/config/_query/no-paging?paging=false`, data)
}

View File

@ -23,5 +23,6 @@ export default {
// 语音/短信获取阿里云模板
getAliTemplate: (id: any) => get(`/notifier/sms/aliyun/${id}/templates`),
// 短信获取签名
getSigns: (id: any) => get(`/notifier/sms/aliyun/${id}/signs`)
getSigns: (id: any) => get(`/notifier/sms/aliyun/${id}/signs`),
getListByConfigId: (id: string, data: any): any => post(`/notifier/template/${id}/_query`, data),
}

View File

@ -34,4 +34,13 @@ export const save = (data:any) =>server.post('/alarm/config',data);
/**
*
*/
export const detail = (id:string) => server.get(`/alarm/config/${id}`);
export const detail = (id:string) => server.get(`/alarm/config/${id}`);
/**
*
*/
export const unbindScene = (id:string,data:any) => server.post(`/alarm/rule/bind/${id}/_delete`,data);
/**
*
*/
export const bindScene = (data:any) => server.patch("/alarm/rule/bind",data)

View File

@ -64,6 +64,12 @@ const iconKeys = [
'ToolOutlined',
'FileOutlined',
'LikeOutlined',
'CaretUpOutlined',
'CaretRightOutlined',
'CaretLeftOutlined',
'CaretDownOutlined',
'MinusOutlined',
'AudioOutlined',
]
const Icon = (props: {type: string}) => {

View File

@ -1,8 +1,8 @@
<template>
<a-badge
<j-badge
:status="statusNames ? statusNames[status] : 'default'"
:text="text"
></a-badge>
></j-badge>
</template>
<script setup lang="ts">

View File

@ -21,7 +21,7 @@
<!-- 勾选 -->
<div v-if="active" class="checked-icon">
<div>
<CheckOutlined />
<AIcon type="CheckOutlined" />
</div>
</div>
@ -62,24 +62,6 @@
}"
>
<slot name="actions" v-bind="item"></slot>
<!-- <a-popconfirm v-if="item.popConfirm" v-bind="item.popConfirm">
<a-button :disabled="item.disabled">
<DeleteOutlined v-if="item.key === 'delete'" />
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
</template>
</a-button>
</a-popconfirm>
<template v-else>
<a-button :disabled="item.disabled">
<DeleteOutlined v-if="item.key === 'delete'" />
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
</template>
</a-button>
</template> -->
</div>
</div>
</slot>
@ -87,13 +69,7 @@
</template>
<script setup lang="ts">
import {
SearchOutlined,
CheckOutlined,
DeleteOutlined,
} from '@ant-design/icons-vue';
import BadgeStatus from '@/components/BadgeStatus/index.vue';
import { StatusColorEnum } from '@/utils/consts.ts';
import type { ActionsType } from '@/components/Table/index.vue';
import { PropType } from 'vue';

View File

@ -1,54 +1,54 @@
<template>
<template v-if="isPermission">
<template v-if="popConfirm">
<a-popconfirm v-bind="popConfirm" :disabled="!isPermission || props.disabled">
<a-tooltip v-if="tooltip" v-bind="tooltip">
<j-popconfirm v-bind="popConfirm" :disabled="!isPermission || props.disabled">
<j-tooltip v-if="tooltip" v-bind="tooltip">
<slot v-if="noButton"></slot>
<a-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
<j-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
<slot></slot>
<template #icon>
<slot name="icon"></slot>
</template>
</a-button>
</a-tooltip>
<a-button v-else v-bind="props" :disabled="_isPermission" >
</j-button>
</j-tooltip>
<j-button v-else v-bind="props" :disabled="_isPermission" >
<slot></slot>
<template #icon>
<slot name="icon"></slot>
</template>
</a-button>
</a-popconfirm>
</j-button>
</j-popconfirm>
</template>
<template v-else-if="tooltip">
<a-tooltip v-bind="tooltip">
<j-tooltip v-bind="tooltip">
<slot v-if="noButton"></slot>
<a-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
<j-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
<slot></slot>
<template #icon>
<slot name="icon"></slot>
</template>
</a-button>
</a-tooltip>
</j-button>
</j-tooltip>
</template>
<template v-else>
<slot v-if="noButton"></slot>
<a-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
<j-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
<slot></slot>
<template #icon>
<slot name="icon"></slot>
</template>
</a-button>
</j-button>
</template>
</template>
<a-tooltip v-else title="没有权限">
<j-tooltip v-else title="没有权限">
<slot v-if="noButton"></slot>
<a-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
<j-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
<slot></slot>
<template #icon>
<slot name="icon"></slot>
</template>
</a-button>
</a-tooltip>
</j-button>
</j-tooltip>
</template>
<script setup lang="ts" name="PermissionButton">
import { CSSProperties, PropType } from 'vue'

View File

@ -0,0 +1,50 @@
<!-- 视频播放 -->
<template>
<vue3videoPlay v-bind="options" />
</template>
<script setup lang="ts">
import 'vue3-video-play/dist/style.css';
import vue3videoPlay from 'vue3-video-play';
const props = defineProps({
src: { type: String, default: '' },
type: { type: String, default: 'mp4' },
});
watch(
() => props.src,
(val: string) => {
options.src = val;
},
);
const options = reactive({
...props,
width: '500px', //
height: '280px', //
color: '#409eff', //
title: '', //
// src: props.src,
// type: props.type,
muted: false, //
webFullScreen: false,
speedRate: ['0.75', '1.0', '1.25', '1.5', '2.0'], //
autoPlay: true, //
loop: false, //
mirror: false, //
ligthOff: false, //
volume: 0.3, //
control: true, //
controlBtns: [
'audioTrack',
'quality',
'speedRate',
'volume',
'setting',
'pip',
'pageFullScreen',
'fullScreen',
], //,
});
</script>

View File

@ -0,0 +1,128 @@
.live-player-tools {
display: flex;
flex-basis: 250px;
flex-direction: column;
justify-content: center;
margin-left: 24px;
padding: 0 12px;
.direction {
position: relative;
display: grid;
grid-gap: 2px;
grid-template-rows: 1fr 1fr;
grid-template-columns: 1fr 1fr;
margin-bottom: 30px;
overflow: hidden;
border-radius: 50%;
transform: rotateZ(45deg);
.direction-item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 100%;
font-size: 36px;
background-color: rgba(#000, 0.1);
transition: background-color 0.3s;
.direction-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotateZ(-45deg);
}
}
.direction-audio {
position: absolute;
top: 50%;
left: 50%;
display: flex;
align-items: center;
justify-content: center;
width: 45%;
height: 45%;
font-size: 30px;
background-color: #fff;
border-radius: 50%;
transform: translate(-50%, -50%) rotateZ(-45deg);
}
.zoom .zoom-item,
& .direction-item {
&:hover {
color: #fff;
background-color: @primary-color-hover;
}
&:active {
color: #fff;
background-color: @primary-color-active;
}
}
> div {
cursor: pointer;
&.disable {
color: @disabled-color;
}
}
}
.zoom {
display: grid;
grid-gap: 2px;
grid-template-columns: 1fr 1fr;
.zoom-item {
padding: 8px 0;
font-size: 24px;
text-align: center;
background-color: rgba(#000, 0.1);
cursor: pointer;
&:hover {
color: #fff;
background-color: @primary-color-hover;
}
&:active {
color: #fff;
background-color: @primary-color-active;
}
}
.zoom-in {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.zoom-out {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
}
}
@media screen {
@media (min-width: 1300px) {
.live-player-tools {
flex-basis: 150px;
margin-left: 16px;
.direction {
.direction-item {
font-size: 24px;
}
}
.zoom {
.zoom-item {
font-size: 16px;
}
}
}
}
}

View File

@ -0,0 +1,66 @@
<!-- 视频播放工具栏 -->
<template>
<div class="live-player-tools">
<div class="direction">
<div
class="direction-item up"
@mousedown="emit('onMouseDown', 'UP')"
@mouseup="emit('onMouseUp', 'UP')"
>
<AIcon class="direction-icon" type="CaretUpOutlined" />
</div>
<div
class="direction-item right"
@mousedown="emit('onMouseDown', 'RIGHT')"
@mouseup="emit('onMouseUp', 'RIGHT')"
>
<AIcon class="direction-icon" type="CaretRightOutlined" />
</div>
<div
class="direction-item left"
@mousedown="emit('onMouseDown', 'LEFT')"
@mouseup="emit('onMouseUp', 'LEFT')"
>
<AIcon class="direction-icon" type="CaretLeftOutlined" />
</div>
<div
class="direction-item down"
@mousedown="emit('onMouseDown', 'DOWN')"
@mouseup="emit('onMouseUp', 'DOWN')"
>
<AIcon class="direction-icon" type="CaretDownOutlined" />
</div>
<div class="direction-audio">
<!-- <AIcon type="AudioOutlined" /> -->
</div>
</div>
<div class="zoom">
<div
class="zoom-item zoom-in"
@mousedown="emit('onMouseDown', 'ZOOM_IN')"
@mouseup="emit('onMouseUp', 'ZOOM_IN')"
>
<AIcon type="PlusOutlined" />
</div>
<div
class="zoom-item zoom-out"
@mousedown="emit('onMouseDown', 'ZOOM_OUT')"
@mouseup="emit('onMouseUp', 'ZOOM_OUT')"
>
<AIcon type="MinusOutlined" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
type Emits = {
(e: 'onMouseDown', type: string): void;
(e: 'onMouseUp', type: string): void;
};
const emit = defineEmits<Emits>();
</script>
<style lang="less" scoped>
@import './mediaTool.less';
</style>

View File

@ -31,7 +31,14 @@ export default [
path: '/account/center',
component: () => import('@/views/account/Center/index.vue')
},
{
path: '/account/NotificationSubscription',
component: () => import('@/views/account/NotificationSubscription/index.vue')
},
{
path: '/account/NotificationRecord',
component: () => import('@/views/account/NotificationRecord/index.vue')
},
// end: 测试用, 可删除
// 初始化

View File

@ -1,37 +1,47 @@
import { defineStore } from "pinia";
export const useAlarmStore = defineStore('alarm',()=>{
const data = reactive({
tab: 'all',
current: {},
solveVisible: false,
logVisible: false,
defaultLevel: [],
columns: [
{
dataIndex: 'alarmConfigName',
title: '告警名称',
// hideInSearch: true,
},
{
dataIndex: 'alarmTime',
title: '告警时间',
valueType: 'dateTime',
},
{
dataIndex: 'description',
title: '说明',
// hideInSearch: true,
},
{
dataIndex: 'action',
title: '操作',
hideInSearch: true,
valueType: 'option',
},
],
})
return {
data
export const useAlarmStore = defineStore('alarm', () => {
const data = reactive({
tab: 'all',
current: {},
solveVisible: false,
logVisible: false,
defaultLevel: [],
columns: [
{
dataIndex: 'alarmConfigName',
title: '告警名称',
// hideInSearch: true,
},
{
dataIndex: 'alarmTime',
title: '告警时间',
valueType: 'dateTime',
},
{
dataIndex: 'description',
title: '说明',
// hideInSearch: true,
},
{
dataIndex: 'action',
title: '操作',
hideInSearch: true,
valueType: 'option',
},
],
})
return {
data
}
})
export const useAlarmConfigurationStore = defineStore('alarmConfigration', () => {
const configurationData = reactive({
current:{
}
})
return { configurationData }
})

View File

@ -6,11 +6,12 @@
width="770px"
@cancel="emits('update:visible', false)"
>
<a-form :model="form" layout="vertical">
<a-form :model="form" layout="vertical" ref="formRef">
<a-row :gutter="24">
<a-col :span="12">
<a-form-item
label="姓名"
name="name"
:rules="[{ required: true, message: '姓名必填' }]"
>
<a-input
@ -78,6 +79,7 @@
<script setup lang="ts">
import { updateMeInfo_api } from '@/api/account/center';
import { message } from 'ant-design-vue';
import { FormInstance } from 'ant-design-vue/es';
import { userInfoType } from '../typing';
const emits = defineEmits(['ok', 'update:visible']);
@ -86,14 +88,16 @@ const props = defineProps<{
data: userInfoType;
}>();
const form = ref(props.data);
const formRef = ref<FormInstance>();
const handleOk = () => {
updateMeInfo_api(form.value).then((resp) => {
if (resp.status === 200) {
message.success('保存成功');
emits('ok');
emits('update:visible', false);
}
formRef.value?.validate().then(() => {
updateMeInfo_api(form.value).then((resp) => {
if (resp.status === 200) {
message.success('保存成功');
emits('ok');
emits('update:visible', false);
}
});
});
};
</script>

View File

@ -118,13 +118,13 @@ const handleOk = () => {
oldPassword: form.value.oldPassword,
newPassword: form.value.newPassword,
};
// updateMepsd_api(params).then((resp) => {
// if (resp.status === 200) {
// message.success('');
// emits('ok');
// emits('update:visible', false);
// }
// });
updateMepsd_api(params).then((resp) => {
if (resp.status === 200) {
message.success('保存成功');
emits('ok');
emits('update:visible', false);
}
});
});
};
console.clear();

View File

@ -51,7 +51,7 @@
</div>
<div class="info-card">
<p>注册时间</p>
<p>{{ userInfo.createTime }}</p>
<p>{{ moment(userInfo.createTime).format('YYYY-MM-DD HH:mm:ss') }}</p>
</div>
<div class="info-card">
<p>电话</p>
@ -216,7 +216,7 @@
</page-container>
</template>
<script setup lang="ts">
<script setup lang="ts" name="Center">
import PermissionButton from '@/components/PermissionButton/index.vue';
import EditInfoDialog from './components/EditInfoDialog.vue';
import EditPasswordDialog from './components/EditPasswordDialog.vue';

View File

@ -0,0 +1,86 @@
<template>
<a-modal
visible
title="详情"
width="1000px"
@ok="emits('update:visible', false)"
@cancel="emits('update:visible', false)"
>
<a-row v-if="data?.targetType === 'device'">
<a-col :span="6" class="label">告警设备</a-col>
<a-col :span="6" class="value">
{{ data?.targetName || '' }}
</a-col>
<a-col :span="6" class="label">设备ID</a-col>
<a-col :span="6" class="value">
{{ data?.targetId || '' }}
</a-col>
</a-row>
<a-row>
<a-col :span="6" class="label">告警名称</a-col>
<a-col :span="6" class="value">
{{ data?.alarmName || data?.alarmConfigName || '' }}
</a-col>
<a-col :span="6" class="label">告警时间</a-col>
<a-col :span="6" class="value">
{{ moment(data?.alarmTime).format('YYYY-MM-DD HH:mm:ss') }}
</a-col>
<a-col :span="6" class="label">告警级别</a-col>
<a-col :span="6" class="value">
{{ (levelList.length > 0 && getLevelLabel(data.level)) || '' }}
</a-col>
<a-col :span="6" class="label">告警说明</a-col>
<a-col :span="6" class="value">{{ data?.description || '' }}</a-col>
<a-col :span="6" class="label">告警流水</a-col>
<a-col :span="18" class="value">
<!-- <MonacoEditor
style="width: 100%; height: 370px"
theme="vs"
v-model="jsonData"
/> -->
<JsonViewer :value="jsonData" copyable boxed sort />
</a-col>
</a-row>
</a-modal>
</template>
<script setup lang="ts">
import MonacoEditor from '@/components/MonacoEditor/index.vue';
import {JsonViewer} from 'vue3-json-viewer';
import { queryLevel as queryLevel_api } from '@/api/rule-engine/config';
import moment from 'moment';
const emits = defineEmits(['update:visible']);
const props = defineProps<{
visible: boolean;
data: any;
}>();
const levelList = ref<any[]>([]);
const data = computed(() => {
if (props.data.detailJson) return JSON.parse(props.data.detailJson);
else return props.data?.detail || props.data;
});
const getLevel = () => {
queryLevel_api().then((resp: any) => {
if (resp.status === 200) levelList.value = resp.result.levels;
});
};
getLevel();
const getLevelLabel = (id: string) => {
if (levelList.value.length < 1 || !id) return '';
const obj = levelList.value.find((item) => item.id === id);
return obj.title;
};
const jsonData = JSON.stringify({
name: 'qiu',
age: 18,
isMan: false,
arr: [1, 2, 5],
});
</script>
<style scoped></style>

View File

@ -0,0 +1,212 @@
<template>
<page-container>
<div class="notification-record-container">
<Search :columns="columns" @search="query.search" />
<JTable
ref="tableRef"
:columns="columns"
:request="getList_api"
model="TABLE"
:params="query.params.value"
:defaultParams="{
'sorts[0].name': 'notifyTime',
'sorts[0].order': 'desc',
}"
>
<template #topicProvider="slotProps">
{{ slotProps.topicName }}
</template>
<template #notifyTime="slotProps">
{{
moment(slotProps.notifyTime).format(
'YYYY-MM-DD HH:mm:ss',
)
}}
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state.value"
:text="slotProps.state.text"
:statusNames="{
read: 'success',
unread: 'error',
}"
></BadgeStatus>
</template>
<template #action="slotProps">
<a-space :size="16">
<PermissionButton
type="link"
:popConfirm="{
title: `确认标为${
slotProps.state.value === 'read'
? '未读'
: '已读'
}`,
onConfirm: () => table.changeStatus(slotProps),
}"
:tooltip="{
title:
slotProps.state.value === 'read'
? '标为未读'
: '标为已读',
}"
>
<AIcon type="ReadIconOutlined" />
<!-- <svg
width="1em"
height="1em"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 18H6L2 22V2C2 2 2.9 2 4 2H20C21.1 2 22 2 22 2V11H20V4H4V16H12V18ZM23 14.34L21.59 12.93L17.35 17.17L15.23 15.05L13.82 16.46L17.34 20L23 14.34Z"
fill="currentColor"
/>
</svg> -->
</PermissionButton>
<PermissionButton
type="link"
:tooltip="{
title: '查看',
}"
@click="table.view(slotProps)"
>
<AIcon type="SearchOutlined" />
</PermissionButton>
</a-space>
</template>
</JTable>
<ViewDialog v-if="viewVisible" v-model:visible="viewVisible" :data="viewItem" />
</div>
</page-container>
</template>
<script setup lang="ts" name="NotificationRecord">
import ViewDialog from './components/ViewDialog.vue';
import PermissionButton from '@/components/PermissionButton/index.vue';
import {
getList_api,
changeStatus_api,
} from '@/api/account/notificationRecord';
import { getTypeList_api } from '@/api/account/notificationSubscription';
import { optionItem } from '@/views/rule-engine/Scene/typings';
import { dictItemType } from '@/views/system/DataSource/typing';
import moment from 'moment';
import { message } from 'ant-design-vue';
const columns = [
{
title: '类型',
dataIndex: 'topicProvider',
key: 'topicProvider',
search: {
type: 'select',
options: () =>
getTypeList_api().then((resp: any) =>
resp.result
.map((item: dictItemType) => ({
label: item.name,
value: item.id,
}))
.filter((item: optionItem) => item.value === 'alarm'),
),
},
scopedSlots: true,
ellipsis: true,
},
{
title: '消息',
dataIndex: 'message',
key: 'message',
search: {
type: 'string',
},
scopedSlots: true,
ellipsis: true,
},
{
title: '通知时间',
dataIndex: 'notifyTime',
key: 'notifyTime',
search: {
type: 'date',
},
scopedSlots: true,
ellipsis: true,
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
search: {
type: 'select',
options: [
{
label: '未读',
value: 'unread',
},
{
label: '已读',
value: 'read',
},
],
},
scopedSlots: true,
ellipsis: true,
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
ellipsis: true,
scopedSlots: true,
},
];
const query = {
params: ref({}),
search: (params: object) => {
query.params.value = { ...params };
},
};
const tableRef = ref();
const table = {
changeStatus: (row: any) => {
const type = row.state.value === 'read' ? '_unread' : '_read';
changeStatus_api(type, [row.id]).then((resp: any) => {
if (resp.status === 200) {
message.success('操作成功!');
table.refresh();
}
});
},
view: (row: any) => {
viewItem.value = row;
viewVisible.value = true;
},
refresh: () => {
tableRef.value && tableRef.value.reload();
},
};
const viewVisible = ref<boolean>(false);
const viewItem = ref<any>({});
</script>
<style lang="less" scoped>
.notification-record-container {
:deep(.ant-table-tbody) {
.ant-table-cell {
.ant-space-item {
.ant-btn-link {
padding: 0;
}
}
}
}
}
</style>

View File

@ -0,0 +1,150 @@
<template>
<a-modal
visible
:title="props.data.id ? '编辑' : '新增'"
width="865px"
@ok="confirm"
@cancel="emits('update:visible', false)"
>
<a-form :model="form" layout="vertical" ref="formRef">
<a-form-item
label="名称"
name="subscribeName"
:rules="[
{ required: true, message: '请输入名称' },
{
max: 64,
message: '最多可输入64个字符',
},
]"
>
<a-input
v-model:value="form.subscribeName"
placeholder="请输入名称"
/>
</a-form-item>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item
label="类型"
name="topicProvider"
:rules="[{ required: true, message: '请选择类型' }]"
>
<a-select
v-model:value="form.topicProvider"
placeholder="请选择类型"
:options="typeList"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
label="告警规则"
:name="['topicConfig', 'alarmConfigId']"
:rules="[{ required: true, message: '请选择告警规则' }]"
>
<a-select
:value="form.topicConfig.alarmConfigId?.split(',')"
:options="alarmList"
placeholder="请选择告警规则"
mode="multiple"
@change="onSelect"
></a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item
name="notice"
label="通知方式"
:rules="[{ required: true, message: '请选择通知方式' }]"
>
<a-checkbox-group
v-model:value="form.notice"
name="checkboxgroup"
:options="[
{
label: '站内通知',
value: 1,
},
]"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script setup lang="ts">
import { rowType } from '../typing';
import {
getTypeList_api,
getAlarmList_api,
save_api,
} from '@/api/account/notificationSubscription';
import { optionsType } from '@/views/system/Department/typing';
import { dictItemType } from '@/views/system/DataSource/typing';
import { optionItem } from '@/views/rule-engine/Scene/typings';
import { FormInstance, message } from 'ant-design-vue';
const emits = defineEmits(['ok', 'update:visible']);
const props = defineProps<{
visible: boolean;
data: rowType;
}>();
const initForm = {
subscribeName: '',
topicConfig: {},
notice: [1],
};
const formRef = ref<FormInstance>();
const form = ref({
...initForm,
...props.data,
});
const confirm = () => {
formRef.value &&
formRef.value.validate().then(() => {
save_api(form.value).then((resp) => {
if (resp.status === 200) {
message.success('操作成功');
emits('ok')
emits('update:visible', false);
}
});
});
};
const typeList = ref<optionsType>([]);
const alarmList = ref<optionsType>([]);
init();
function init() {
getTypeList_api().then((resp: any) => {
if (resp.status === 200)
typeList.value = resp.result
.map((item: dictItemType) => ({
label: item.name,
value: item.id,
}))
.filter((item: optionItem) => item.value === 'alarm');
});
getAlarmList_api().then((resp: any) => {
if (resp.status === 200)
alarmList.value = resp.result.map((item: dictItemType) => ({
label: item.name,
value: item.id,
}));
});
}
function onSelect(keys: string[], items: optionsType) {
form.value.topicConfig = {
alarmConfigId: keys.join(','),
alarmConfigName: items.map((item) => item.label).join(','),
};
}
</script>
<style scoped></style>

View File

@ -0,0 +1,203 @@
<template>
<page-container>
<div class="notification-subscription-container">
<Search :columns="columns" @search="query.search" />
<JTable
ref="tableRef"
:columns="columns"
:request="getNoticeList_api"
model="TABLE"
:params="query.params.value"
:defaultParams="{
sorts: [{ name: 'notifyTime', order: 'desc' }],
}"
>
<template #headerTitle>
<PermissionButton type="primary" @click="table.edit()">
<AIcon type="PlusOutlined" />新增
</PermissionButton>
</template>
<template #alarmConfigName="slotProps">
{{ slotProps.topicConfig.alarmConfigName }}
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state.value"
:text="slotProps.state.text"
:statusNames="{
enabled: 'success',
disabled: 'error',
}"
></BadgeStatus>
</template>
<template #action="slotProps">
<a-space :size="16">
<PermissionButton
type="link"
:tooltip="{
title: '编辑',
}"
@click="table.edit(slotProps)"
>
<AIcon type="EditOutlined" />
</PermissionButton>
<PermissionButton
type="link"
:popConfirm="{
title: `确定${
slotProps.state.value === 'enabled'
? '禁用'
: '启用'
}`,
onConfirm: () => table.changeStatus(slotProps),
}"
:tooltip="{
title:
slotProps.state.value === 'enabled'
? '禁用'
: '启用',
}"
>
<AIcon
:type="
slotProps.state.value === 'enabled'
? 'StopOutlined'
: 'PlayCircleOutlined'
"
/>
</PermissionButton>
<PermissionButton
type="link"
:tooltip="{
title:
slotProps.state.value === 'enabled'
? '请先禁用,再删除'
: '删除',
}"
:popConfirm="{
title: `确认删除?`,
onConfirm: () => table.delete(slotProps),
}"
:disabled="slotProps.state.value === 'enabled'"
>
<AIcon type="DeleteOutlined" />
</PermissionButton>
</a-space>
</template>
</JTable>
<EditDialog
v-if="dialogVisible"
v-model:visible="dialogVisible"
:data="table.seletctRow"
@ok="table.refresh"
/>
</div>
</page-container>
</template>
<script setup lang="ts" name="NotificationSubscription">
import PermissionButton from '@/components/PermissionButton/index.vue';
import EditDialog from './components/EditDialog.vue';
import {
getNoticeList_api,
changeStatus_api,
remove_api
} from '@/api/account/notificationSubscription';
import { rowType } from './typing';
import { message } from 'ant-design-vue';
const columns = [
{
title: '名称',
dataIndex: 'subscribeName',
key: 'subscribeName',
ellipsis: true,
search: {
type: 'string',
},
},
{
title: '类型',
dataIndex: 'topicName',
key: 'topicName',
scopedSlots: true,
ellipsis: true,
},
{
title: '告警规则',
dataIndex: 'alarmConfigName',
key: 'alarmConfigName',
scopedSlots: true,
ellipsis: true,
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
scopedSlots: true,
ellipsis: true,
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
ellipsis: true,
scopedSlots: true,
},
];
const query = {
params: ref({}),
search: (params: object) => {
query.params.value = {...params};
},
};
const dialogVisible = ref<boolean>(false);
const tableRef = ref();
const table = {
seletctRow: ref<rowType>(),
edit: (row?: rowType) => {
table.seletctRow = {
...(row || ({} as any)),
};
dialogVisible.value = true;
},
changeStatus: (row: rowType) => {
const status = row.state.value === 'enabled' ? '_disabled' : '_enabled';
changeStatus_api(row.id as string, status).then((resp) => {
if (resp.status === 200) {
message.success('操作成功!');
table.refresh();
} else message.warning('操作失败!');
});
},
delete: (row: rowType) => {
remove_api(row.id as string).then(resp=>{
if(resp.status === 200) {
message.success('操作成功!')
table.refresh()
}else message.warning('操作失败!')
})
},
refresh: () => {
tableRef.value && tableRef.value.reload();
},
};
</script>
<style lang="less" scoped>
.notification-subscription-container {
:deep(.ant-table-tbody) {
.ant-table-cell {
.ant-space-item {
.ant-btn-link {
padding: 0;
}
}
}
}
}
</style>

View File

@ -0,0 +1,14 @@
export type rowType = {
id?: string;
locale: string;
state: { text: string, value: "enabled" | 'disabled' };
subscribeName: string;
subscriber: string;
subscriberType: string;
topicConfig: { alarmConfigId?: string, alarmConfigName?: string };
alarmConfigId: string;
alarmConfigName: stirng;
topicName: string;
topicProvider: string| undefined;
notice?:any[]
}

View File

@ -1,5 +1,5 @@
<template>
<a-drawer placement="right" :closable="false" :visible="true">
<j-drawer placement="right" :closable="false" :visible="true">
<template #title>
<div
style="
@ -15,21 +15,21 @@
@click="onClose"
/></span
>
<a-button type="primary" @click="saveBtn">保存</a-button>
<j-button type="primary" @click="saveBtn">保存</j-button>
</div>
</template>
<a-form layout="vertical" ref="formRef" :model="modelRef">
<j-form layout="vertical" ref="formRef" :model="modelRef">
<template v-for="(item, index) in props.config" :key="index">
<a-form-item
<j-form-item
:name="item.property"
v-for="i in item.properties"
:key="i.property"
>
<template #label>
<span style="margin-right: 5px">{{ i.name }}</span>
<a-tooltip v-if="i.description" :title="i.description"
<j-tooltip v-if="i.description" :title="i.description"
><AIcon type="QuestionCircleOutlined"
/></a-tooltip>
/></j-tooltip>
</template>
<ValueItem
v-model:modelValue="modelRef[i.property]"
@ -45,10 +45,10 @@
: undefined
"
/>
</a-form-item>
</j-form-item>
</template>
</a-form>
</a-drawer>
</j-form>
</j-drawer>
</template>
<script lang="ts" setup>

View File

@ -2,7 +2,7 @@
<div style="margin-top: 20px" v-if="config.length">
<div style="display: flex; margin-bottom: 20px; align-items: center">
<div style="font-size: 16px; font-weight: 700">配置</div>
<a-space>
<j-space>
<PermissionButton
type="link"
@click="visible = true"
@ -20,10 +20,10 @@
}"
hasPermission="device/Instance:update"
>
<AIcon type="CheckOutlined" />应用配置<a-tooltip
<AIcon type="CheckOutlined" />应用配置<j-tooltip
title="修改配置后需重新应用后才能生效。"
><AIcon type="QuestionCircleOutlined"
/></a-tooltip>
/></j-tooltip>
</PermissionButton>
<PermissionButton
type="link"
@ -34,26 +34,26 @@
}"
hasPermission="device/Instance:update"
>
<AIcon type="SyncOutlined" />恢复默认<a-tooltip
<AIcon type="SyncOutlined" />恢复默认<j-tooltip
title="该设备单独编辑过配置信息,点击此将恢复成默认的配置信息,请谨慎操作。"
><AIcon type="QuestionCircleOutlined"
/></a-tooltip>
/></j-tooltip>
</PermissionButton>
</a-space>
</j-space>
</div>
<a-descriptions bordered size="small" v-for="i in config" :key="i.name">
<j-descriptions bordered size="small" v-for="i in config" :key="i.name">
<template #title
><h4 style="font-size: 15px">{{ i.name }}</h4></template
>
<a-descriptions-item
<j-descriptions-item
v-for="item in i.properties"
:key="item.property"
>
<template #label>
<span style="margin-right: 5px">{{ item.name }}</span>
<a-tooltip v-if="item.description" :title="item.description"
<j-tooltip v-if="item.description" :title="item.description"
><AIcon type="QuestionCircleOutlined"
/></a-tooltip>
/></j-tooltip>
</template>
<span
v-if="
@ -68,7 +68,7 @@
instanceStore.current?.configuration?.[item.property] ||
''
}}</span>
<a-tooltip
<j-tooltip
v-if="isExit(item.property)"
:title="`有效值:${
instanceStore.current?.configuration?.[
@ -76,10 +76,10 @@
]
}`"
><AIcon type="QuestionCircleOutlined"
/></a-tooltip>
/></j-tooltip>
</span>
</a-descriptions-item>
</a-descriptions>
</j-descriptions-item>
</j-descriptions>
<Save
v-if="visible"
@save="saveBtn"

View File

@ -1,5 +1,5 @@
<template>
<a-drawer placement="right" :closable="false" :visible="true">
<j-drawer placement="right" :closable="false" :visible="true">
<template #title>
<div
style="
@ -15,32 +15,32 @@
@click="onClose"
/></span
>
<a-button type="primary" @click="saveBtn">保存</a-button>
<j-button type="primary" @click="saveBtn">保存</j-button>
</div>
</template>
<a-form layout="vertical" ref="formRef" :model="modelRef">
<a-form-item
<j-form layout="vertical" ref="formRef" :model="modelRef">
<j-form-item
:name="item.relation"
:label="item.relationName"
v-for="(item, index) in dataSource"
:key="index"
>
<a-select
<j-select
showSearch
mode="multiple"
v-model:value="modelRef[item.relation]"
:placeholder="`请选择${item.relationName}`"
>
<a-select-option
<j-select-option
:value="item.value"
v-for="item in userList"
:key="item.id"
>{{ item.name }}</a-select-option
>{{ item.name }}</j-select-option
>
</a-select>
</a-form-item>
</a-form>
</a-drawer>
</j-select>
</j-form-item>
</j-form>
</j-drawer>
</template>
<script lang="ts" setup>

View File

@ -1,6 +1,6 @@
<template>
<div style="margin-top: 20px">
<a-descriptions bordered>
<j-descriptions bordered>
<template #title>
关系信息
<PermissionButton
@ -8,13 +8,13 @@
@click="visible = true"
hasPermission="device/Instance:update"
>
<AIcon type="EditOutlined" />编辑<a-tooltip
<AIcon type="EditOutlined" />编辑<j-tooltip
title="管理设备与其他业务的关联关系,关系来源于关系配置"
><AIcon type="QuestionCircleOutlined"
/></a-tooltip>
/></j-tooltip>
</PermissionButton>
</template>
<a-descriptions-item
<j-descriptions-item
:span="1"
v-for="item in dataSource"
:key="item.objectId"
@ -23,9 +23,9 @@
item?.related
? (item?.related || []).map((i) => i.name).join(',')
: ''
}}</a-descriptions-item
}}</j-descriptions-item
>
</a-descriptions>
</j-descriptions>
<Save v-if="visible" @save="saveBtn" @close="visible = false" />
</div>
</template>

View File

@ -1,15 +1,15 @@
<template>
<a-modal
<j-modal
:width="1000"
:visible="true"
title="编辑标签"
@ok="handleOk"
@cancel="handleCancel"
>
<a-table
<j-table
rowKey="id"
:columns="columns"
:data-source="dataSource"
:datj-source="dataSource"
bordered
:pagination="false"
>
@ -43,8 +43,8 @@
</template>
</div>
</template>
</a-table>
</a-modal>
</j-table>
</j-modal>
</template>
<script lang="ts" setup>

View File

@ -1,6 +1,6 @@
<template>
<div style="margin-top: 20px">
<a-descriptions bordered>
<j-descriptions bordered>
<template #title>
标签
<PermissionButton
@ -11,14 +11,14 @@
<AIcon type="EditOutlined" />编辑
</PermissionButton>
</template>
<a-descriptions-item
<j-descriptions-item
:span="1"
v-for="item in dataSource"
:key="item.key"
:label="`${item.name}${item.key})`"
>{{ item?.value }}</a-descriptions-item
>{{ item?.value }}</j-descriptions-item
>
</a-descriptions>
</j-descriptions>
<Save v-if="visible" @close="visible = false" @save="saveBtn" />
</div>
</template>

View File

@ -1,6 +1,6 @@
<template>
<a-card>
<a-descriptions bordered>
<j-card>
<j-descriptions bordered>
<template #title>
设备信息
<PermissionButton
@ -12,59 +12,59 @@
编辑
</PermissionButton>
</template>
<a-descriptions-item label="设备ID">{{
<j-descriptions-item label="设备ID">{{
instanceStore.current.id
}}</a-descriptions-item>
<a-descriptions-item label="产品名称">{{
}}</j-descriptions-item>
<j-descriptions-item label="产品名称">{{
instanceStore.current.productName
}}</a-descriptions-item>
<a-descriptions-item label="产品分类">{{
}}</j-descriptions-item>
<j-descriptions-item label="产品分类">{{
instanceStore.current.classifiedName
}}</a-descriptions-item>
<a-descriptions-item label="设备类型">{{
}}</j-descriptions-item>
<j-descriptions-item label="设备类型">{{
instanceStore.current.deviceType?.text
}}</a-descriptions-item>
<a-descriptions-item label="固件版本">{{
}}</j-descriptions-item>
<j-descriptions-item label="固件版本">{{
instanceStore.current.firmwareInfo?.version
}}</a-descriptions-item>
<a-descriptions-item label="连接协议">{{
}}</j-descriptions-item>
<j-descriptions-item label="连接协议">{{
instanceStore.current?.protocolName
}}</a-descriptions-item>
<a-descriptions-item label="消息协议">{{
}}</j-descriptions-item>
<j-descriptions-item label="消息协议">{{
instanceStore.current.transport
}}</a-descriptions-item>
<a-descriptions-item label="创建时间">{{
}}</j-descriptions-item>
<j-descriptions-item label="创建时间">{{
instanceStore.current.createTime
? moment(instanceStore.current.createTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}</a-descriptions-item>
<a-descriptions-item label="注册时间">{{
}}</j-descriptions-item>
<j-descriptions-item label="注册时间">{{
instanceStore.current.registerTime
? moment(instanceStore.current.registerTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}</a-descriptions-item>
<a-descriptions-item label="最后上线时间">{{
}}</j-descriptions-item>
<j-descriptions-item label="最后上线时间">{{
instanceStore.current.onlineTime
? moment(instanceStore.current.onlineTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}</a-descriptions-item>
<a-descriptions-item
}}</j-descriptions-item>
<j-descriptions-item
label="父设备"
v-if="
instanceStore.current.deviceType?.value === 'childrenDevice'
"
>{{ instanceStore.current.parentId }}</a-descriptions-item
>{{ instanceStore.current.parentId }}</j-descriptions-item
>
<a-descriptions-item label="说明">{{
<j-descriptions-item label="说明">{{
instanceStore.current.description
}}</a-descriptions-item>
</a-descriptions>
}}</j-descriptions-item>
</j-descriptions>
<Config />
<Tags
v-if="
@ -84,7 +84,7 @@
@close="visible = false"
@save="saveBtn"
/>
</a-card>
</j-card>
</template>
<script lang="ts" setup>

View File

@ -1,11 +1,11 @@
<template>
<a-card>
<j-card>
<Search
:columns="columns"
target="device-instance-log"
@search="handleSearch"
/>
<JTable
<JProTable
ref="instanceRefLog"
:columns="columns"
:request="(e: Record<string, any>) => queryLog(instanceStore.current.id, e)"
@ -26,23 +26,23 @@
}}
</template>
<template #action="slotProps">
<a-space>
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
<a-button
<j-button
@click="i.onClick"
type="link"
style="padding: 0px"
>
<template #icon><AIcon :type="i.icon" /></template>
</a-button>
</j-button>
</template>
</a-space>
</j-space>
</template>
</JTable>
</a-card>
</JProTable>
</j-card>
</template>
<script lang="ts" setup>

View File

@ -1,6 +1,6 @@
<template>
<a-card>
<j-card>
<div>
<div class="top">
<div class="top-left">
@ -25,9 +25,9 @@
</div>
<div>
脚本语言:
<a-select :defaultValue="'JavaScript'" style="width: 200;margin-left: 5px;">
<a-select-option value="JavaScript">JavaScript(ECMAScript 5)</a-select-option>
</a-select>
<j-select :defaultValue="'JavaScript'" style="width: 200;margin-left: 5px;">
<j-select-option value="JavaScript">JavaScript(ECMAScript 5)</j-select-option>
</j-select>
<AIcon type="ExpandOutlined" style="margin-left: 20px;" @click="toggle" />
</div>
</div>
@ -42,7 +42,7 @@
})
}"></div>
<MonacoEditor language="javascript" style="height: 100%;" theme="vs" v-model:modelValue="editorValue" />
<j-monaco-editor language="javascript" style="height: 100%;" theme="vs" v-model:modelValue="editorValue" />
</div>
<div class="bottom">
<div style="width: 49.5%;">
@ -51,25 +51,25 @@
<div class="bottom-title-topic">
<template v-if="instanceStore.current.transport === 'MQTT'">
<div style="margin-right: 5px;">Topic:</div>
<a-auto-complete placeholder="请输入Topic" style="width: 300px" :options="topicList"
<j-auto-complete placeholder="请输入Topic" style="width: 300px" :options="topicList"
:allowClear="true" :filterOption="(inputValue: any, option: any) =>
option!.value.indexOf(inputValue) !== -1" v-model:value="topic" />
</template>
<template v-else>
<div style="margin-right: 5px;">URL:</div>
<a-input placeholder="请输入URL" v-model:value="url" style="width: 300px"></a-input>
<j-input placeholder="请输入URL" v-model:value="url" style="width: 300px"></j-input>
</template>
</div>
</div>
<a-textarea :rows="5" placeholder="// 二进制数据以0x开头的十六进制输入字符串数据输入原始字符串" style="margin-top: 10px;"
<j-textarea :rows="5" placeholder="// 二进制数据以0x开头的十六进制输入字符串数据输入原始字符串" style="margin-top: 10px;"
v-model:value="simulation" />
</div>
<div style="width: 49.5%;">
<div class="bottom-title">
<div class="bottom-title-text">运行结果</div>
</div>
<a-textarea :autoSize="{ minRows: 5 }" :style="resStyle" v-model:value="result" />
<j-textarea :autoSize="{ minRows: 5 }" :style="resStyle" v-model:value="result" />
</div>
</div>
</div>
@ -87,13 +87,13 @@
保存
</PermissionButton>
</div>
</a-card>
</j-card>
</template>
<script setup lang='ts' name="Parsing">
import AIcon from '@/components/AIcon'
import PermissionButton from '@/components/PermissionButton/index.vue'
import MonacoEditor from '@/components/MonacoEditor/index.vue';
// import MonacoEditor from '@/components/MonacoEditor/index.vue';
import { useFullscreen } from '@vueuse/core'
import { useInstanceStore } from '@/store/instance';
import {

View File

@ -1,12 +1,12 @@
<template>
<a-spin :spinning="loading">
<JTable
<j-spin :spinning="loading">
<JProTable
:columns="columns"
:dataSource="dataSource"
:bodyStyle="{ padding: '0 0 0 20px' }"
>
<template #headerTitle>
<a-input-search
<j-input-search
placeholder="请输入名称"
style="width: 300px; margin-bottom: 10px"
@search="onSearch"
@ -31,18 +31,18 @@
{{ propertyValue[slotProps?.id]?.timeString || '--' }}
</template>
<template #action="slotProps">
<a-space :size="16">
<j-space :size="16">
<template v-for="i in getActions(slotProps)" :key="i.key">
<a-tooltip v-bind="i.tooltip" v-if="i.key !== 'edit'">
<a-button
<j-tooltip v-bind="i.tooltip" v-if="i.key !== 'edit'">
<j-button
style="padding: 0"
type="link"
:disabled="i.disabled"
@click="i.onClick && i.onClick(slotProps)"
>
<AIcon :type="i.icon" />
</a-button>
</a-tooltip>
</j-button>
</j-tooltip>
<PermissionButton
:disabled="i.disabled"
v-else
@ -56,10 +56,10 @@
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</a-space>
</j-space>
</template>
<template #paginationRender>
<a-pagination
<j-pagination
size="small"
:total="total"
:showQuickJumper="false"
@ -78,8 +78,8 @@
@change="pageChange"
/>
</template>
</JTable>
</a-spin>
</JProTable>
</j-spin>
<Save v-if="editVisible" @close="editVisible = false" :data="currentInfo" />
<Indicators
v-if="indicatorVisible"

View File

@ -1,15 +1,15 @@
<template>
<a-card>
<j-card>
<div class="property-box">
<div class="property-box-left">
<a-input-search
<j-input-search
v-model:value="value"
placeholder="请输入事件名称"
style="width: 200px; margin-bottom: 10px"
@search="onSearch"
:allowClear="true"
/>
<a-tabs
<j-tabs
tab-position="left"
style="height: 600px"
v-if="tabList.length"
@ -17,12 +17,12 @@
:tabBarStyle="{ width: '200px' }"
@change="tabChange"
>
<a-tab-pane
<j-tab-pane
v-for="i in tabList"
:key="i.key"
:tab="i.tab"
/>
</a-tabs>
</j-tabs>
<JEmpty v-else style="margin: 250px 0" />
</div>
<div class="property-box-right">
@ -30,7 +30,7 @@
<Property v-else :data="properties" />
</div>
</div>
</a-card>
</j-card>
</template>
<script lang="ts" setup>

View File

@ -1,88 +1,88 @@
<template>
<a-modal
<j-modal
:maskClosable="false"
width="650px"
:visible="true"
:title="!!props.data.id ? '编辑' : '新增'"
:title="!!data?.id ? '编辑' : '新增'"
@ok="handleSave"
@cancel="handleCancel"
:confirmLoading="loading"
>
<div style="margin-top: 10px">
<a-form
<j-form
:layout="'vertical'"
ref="formRef"
:rules="rules"
:model="modelRef"
>
<a-row type="flex">
<a-col flex="180px">
<a-form-item name="photoUrl">
<j-row type="flex">
<j-col flex="180px">
<j-form-item name="photoUrl">
<JUpload v-model="modelRef.photoUrl" />
</a-form-item>
</a-col>
<a-col flex="auto">
<a-form-item name="id">
</j-form-item>
</j-col>
<j-col flex="auto">
<j-form-item name="id">
<template #label>
<span>
ID
<a-tooltip title="若不填写系统将自动生成唯一ID">
<j-tooltip title="若不填写系统将自动生成唯一ID">
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px;" />
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-input
<j-input
v-model:value="modelRef.id"
placeholder="请输入ID"
:disabled="!!props.data.id"
:disabled="!!data?.id"
/>
</a-form-item>
<a-form-item label="名称" name="name">
<a-input
</j-form-item>
<j-form-item label="名称" name="name">
<j-input
v-model:value="modelRef.name"
placeholder="请输入名称"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item name="productId">
</j-form-item>
</j-col>
</j-row>
<j-form-item name="productId">
<template #label>
<span>所属产品
<a-tooltip title="只能选择“正常”状态的产品">
<j-tooltip title="只能选择“正常”状态的产品">
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px" />
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-select
<j-select
showSearch
v-model:value="modelRef.productId"
placeholder="请选择所属产品"
:filter-option="filterOption"
>
<a-select-option
<j-select-option
:value="item.id"
v-for="item in productList"
:key="item.id"
:label="item.name"
:disabled="!!props.data.id"
>{{item.name}}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="说明" name="describe">
<a-textarea
:disabled="!!data?.id"
>{{item.name}}</j-select-option>
</j-select>
</j-form-item>
<j-form-item label="说明" name="describe">
<j-textarea
v-model:value="modelRef.describe"
placeholder="请输入说明"
showCount
:maxlength="200"
/>
</a-form-item>
</a-form>
</j-form-item>
</j-form>
</div>
</a-modal>
</j-modal>
</template>
<script lang="ts" setup>
@ -157,7 +157,7 @@ const rules = {
message: '最多输入64个字符',
},
{
pattern: /^[a-zA-Z0-9_\-]+$/,
pattern: /^[j-zA-Z0-9_\-]+$/,
message: '请输入英文或者数字或者-或者_',
},
{

View File

@ -1,11 +1,11 @@
<template>
<page-container>
<Search
<JSearch
:columns="columns"
target="device-instance"
@search="handleSearch"
/>
<JTable
<JProTable
ref="instanceRef"
:columns="columns"
:request="query"
@ -18,7 +18,7 @@
:params="params"
>
<template #headerTitle>
<a-space>
<j-space>
<PermissionButton
type="primary"
@click="handleAdd"
@ -27,13 +27,13 @@
<template #icon><AIcon type="PlusOutlined" /></template>
新增
</PermissionButton>
<a-dropdown>
<a-button
<j-dropdown>
<j-button
>批量操作 <AIcon type="DownOutlined"
/></a-button>
/></j-button>
<template #overlay>
<a-menu>
<a-menu-item>
<j-menu>
<j-menu-item>
<PermissionButton
@click="exportVisible = true"
hasPermission="device/Instance:export"
@ -43,8 +43,8 @@
/></template>
批量导出设备
</PermissionButton>
</a-menu-item>
<a-menu-item>
</j-menu-item>
<j-menu-item>
<PermissionButton
@click="importVisible = true"
hasPermission="device/Instance:import"
@ -54,8 +54,8 @@
/></template>
批量导入设备
</PermissionButton>
</a-menu-item>
<a-menu-item>
</j-menu-item>
<j-menu-item>
<PermissionButton
ghost
type="primary"
@ -70,8 +70,8 @@
/></template>
激活全部设备
</PermissionButton>
</a-menu-item>
<a-menu-item>
</j-menu-item>
<j-menu-item>
<PermissionButton
type="primary"
@click="syncDeviceStatus"
@ -82,8 +82,8 @@
/></template>
同步设备状态
</PermissionButton>
</a-menu-item>
<a-menu-item v-if="_selectedRowKeys.length">
</j-menu-item>
<j-menu-item v-if="_selectedRowKeys.length">
<PermissionButton
type="primary"
danger
@ -98,8 +98,8 @@
/></template>
删除选中设备
</PermissionButton>
</a-menu-item>
<a-menu-item v-if="_selectedRowKeys.length">
</j-menu-item>
<j-menu-item v-if="_selectedRowKeys.length">
<PermissionButton
type="primary"
:popConfirm="{
@ -113,8 +113,8 @@
/></template>
激活选中设备
</PermissionButton>
</a-menu-item>
<a-menu-item v-if="_selectedRowKeys.length">
</j-menu-item>
<j-menu-item v-if="_selectedRowKeys.length">
<PermissionButton
type="primary"
danger
@ -129,11 +129,11 @@
/></template>
禁用选中设备
</PermissionButton>
</a-menu-item>
</a-menu>
</j-menu-item>
</j-menu>
</template>
</a-dropdown>
</a-space>
</j-dropdown>
</j-space>
</template>
<template #card="slotProps">
<CardBox
@ -160,22 +160,22 @@
{{ slotProps.name }}
</span>
</Ellipsis>
<a-row style="margin-top: 20px">
<a-col :span="12">
<j-row style="margin-top: 20px">
<j-col :span="12">
<div class="card-item-content-text">
设备类型
</div>
<div>{{ slotProps.deviceType?.text }}</div>
</a-col>
<a-col :span="12">
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
产品名称
</div>
<Ellipsis style="width: 100%">
{{ slotProps.productName }}
</Ellipsis>
</a-col>
</a-row>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
@ -200,13 +200,13 @@
</CardBox>
</template>
<template #state="slotProps">
<a-badge
<j-badge
:text="slotProps.state?.text"
:status="statusMap.get(slotProps.state?.value)"
/>
</template>
<template #action="slotProps">
<a-space>
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
@ -225,9 +225,9 @@
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</a-space>
</j-space>
</template>
</JTable>
</JProTable>
</page-container>
<Import v-if="importVisible" @close="importVisible = false" />
<Export

View File

@ -1,18 +1,18 @@
<template>
<a-card>
<j-card>
<div>
<div class="top">
<div>
脚本语言:
<a-select :defaultValue="'JavaScript'" style="width: 200;margin-left: 5px;">
<a-select-option value="JavaScript">JavaScript(ECMAScript 5)</a-select-option>
</a-select>
<j-select :defaultValue="'JavaScript'" style="width: 200;margin-left: 5px;">
<j-select-option value="JavaScript">JavaScript(ECMAScript 5)</j-select-option>
</j-select>
<AIcon type="ExpandOutlined" style="margin-left: 20px;" @click="toggle" />
</div>
</div>
<div class="edit" ref="el">
<MonacoEditor language="javascript" style="height: 100%;" theme="vs" v-model:modelValue="editorValue" />
<j-monaco-editor language="javascript" style="height: 100%;" theme="vs" v-model:modelValue="editorValue" />
</div>
<div class="bottom">
<div style="width: 49.5%;">
@ -21,25 +21,25 @@
<div class="bottom-title-topic">
<template v-if="productStore.current.transportProtocol === 'MQTT'">
<div style="margin-right: 5px;">Topic:</div>
<a-auto-complete placeholder="请输入Topic" style="width: 300px" :options="topicList"
<j-auto-complete placeholder="请输入Topic" style="width: 300px" :options="topicList"
:allowClear="true" :filterOption="(inputValue: any, option: any) =>
option!.value.indexOf(inputValue) !== -1" v-model:value="topic" />
</template>
<template v-else>
<div style="margin-right: 5px;">URL:</div>
<a-input placeholder="请输入URL" v-model:value="url" style="width: 300px"></a-input>
<j-input placeholder="请输入URL" v-model:value="url" style="width: 300px"></j-input>
</template>
</div>
</div>
<a-textarea :rows="5" placeholder="// 二进制数据以0x开头的十六进制输入字符串数据输入原始字符串" style="margin-top: 10px;"
<j-textarea :rows="5" placeholder="// 二进制数据以0x开头的十六进制输入字符串数据输入原始字符串" style="margin-top: 10px;"
v-model:value="simulation" />
</div>
<div style="width: 49.5%;">
<div class="bottom-title">
<div class="bottom-title-text">运行结果</div>
</div>
<a-textarea :autoSize="{ minRows: 5 }" :style="resStyle" v-model:value="result" />
<j-textarea :autoSize="{ minRows: 5 }" :style="resStyle" v-model:value="result" />
</div>
</div>
</div>
@ -57,13 +57,13 @@
保存
</PermissionButton>
</div>
</a-card>
</j-card>
</template>
<script setup lang='ts' name="DataAnalysis">
import AIcon from '@/components/AIcon'
import PermissionButton from '@/components/PermissionButton/index.vue'
import MonacoEditor from '@/components/MonacoEditor/index.vue';
// import MonacoEditor from '@/components/MonacoEditor/index.vue';
import { useFullscreen } from '@vueuse/core'
import { useProductStore } from '@/store/product';
import {

View File

@ -1,5 +1,5 @@
<template>
<JTable :loading="loading" :data-source="data" size="small" :columns="columns" row-key="id" model="TABLE">
<j-pro-table :loading="loading" :data-source="data" size="small" :columns="columns" row-key="id" model="TABLE">
<template #headerTitle>
<a-input-search v-model:value="searchValue" placeholder="请输入名称" @search="handleSearch"></a-input-search>
</template>
@ -28,12 +28,12 @@
{{ sourceMap[slotProps.expands?.source] }}
</template>
<template #type="slotProps">
<a-tag v-for="item in (slotProps.expands?.type || [])" :key="item">
<j-tag v-for="item in (slotProps.expands?.type || [])" :key="item">
{{ expandsType[item] }}
</a-tag>
</j-tag>
</template>
<template #action="slotProps">
<a-space>
<j-space>
<PermissionButton :uhas-permission="`${permission}:update`" type="link" key="edit" style="padding: 0"
:udisabled="operateLimits('updata', type)" @click="handleEditClick(slotProps)" :tooltip="{
title: operateLimits('updata', type) ? '当前的存储方式不支持编辑' : '编辑',
@ -50,14 +50,13 @@
}">
<Aicon type="DeleteOutlined" />
</PermissionButton>
</a-space>
</j-space>
</template>
</JTable>
</j-pro-table>
</template>
<script setup lang="ts" name="BaseMetadata">
import type { MetadataItem, MetadataType } from '@/views/device/Product/typings'
import MetadataMapping from './columns'
import JTable from '@/components/Table'
import { useInstanceStore } from '@/store/instance'
import { useProductStore } from '@/store/product'
import { useMetadataStore } from '@/store/metadata'

View File

@ -15,7 +15,7 @@
组装上报设备的数据您可以导出完整物模型用于云端应用开发
</p>
</div>
<a-tabs @change="handleConvertMetadata">
<a-tabs @change="handleConvertMetadata" destroy-inactive-tab-pane>
<a-tab-pane v-for="item in codecs" :key="item.id" :tab="item.name">
<div class="cat-panel">
<MonacoEditor v-model="value" theme="vs" style="height: 100%"></MonacoEditor>

View File

@ -1,8 +1,8 @@
<template>
<a-card>
<j-card>
<div class='device-detail-metadata' style="position: relative;">
<div class="tips">
<a-tooltip :title="instanceStore.detail?.independentMetadata && type === 'device'
<j-tooltip :title="instanceStore.detail?.independentMetadata && type === 'device'
? '该设备已脱离产品物模型,修改产品物模型对该设备无影响'
: '设备会默认继承产品的物模型,修改设备物模型后将脱离产品物模型'">
<div class="ellipsis">
@ -13,11 +13,11 @@
: '设备会默认继承产品的物模型,修改设备物模型后将脱离产品物模型'
}}
</div>
</a-tooltip>
</j-tooltip>
</div>
<a-tabs class="metadataNav" destroyInactiveTabPane type="card">
<j-tabs class="metadataNav" destroyInactiveTabPane type="card">
<template #rightExtra>
<a-space>
<j-space>
<PermissionButton v-if="type === 'device' && instanceStore.detail?.independentMetadata"
:hasPermission="`${permission}:update`" :popConfirm="{ title: '确认重置?', onConfirm: resetMetadata, }"
:tooltip="{ title: '重置后将使用产品的物模型配置' }" key="reload">
@ -25,32 +25,31 @@
</PermissionButton>
<PermissionButton :hasPermission="`${permission}:update`" @click="visible = true">快速导入</PermissionButton>
<PermissionButton :hasPermission="`${permission}:update`" @click="cat = true">物模型TSL</PermissionButton>
</a-space>
</j-space>
</template>
<a-tab-pane tab="属性定义" key="properties">
<j-tab-pane tab="属性定义" key="properties">
<BaseMetadata :target="type" type="properties" :permission="permission" />
</a-tab-pane>
<a-tab-pane tab="功能定义" key="functions">
</j-tab-pane>
<j-tab-pane tab="功能定义" key="functions">
<BaseMetadata :target="type" type="functions" :permission="permission" />
</a-tab-pane>
<a-tab-pane tab="事件定义" key="events">
</j-tab-pane>
<j-tab-pane tab="事件定义" key="events">
<BaseMetadata :target="type" type="events" :permission="permission" />
</a-tab-pane>
<a-tab-pane tab="标签定义" key="tags">
</j-tab-pane>
<j-tab-pane tab="标签定义" key="tags">
<BaseMetadata :target="type" type="tags" :permission="permission" />
</a-tab-pane>
</a-tabs>
</j-tab-pane>
</j-tabs>
<Import v-model:visible="visible" :type="type" @close="visible = false" />
<Cat v-model:visible="cat" @close="cat = false" :type="type" />
</div>
</a-card>
</j-card>
</template>
<script setup lang="ts" name="Metadata">
import PermissionButton from '@/components/PermissionButton/index.vue'
import { deleteMetadata } from '@/api/device/instance.js'
import { message } from 'ant-design-vue'
import { SystemConst } from '@/utils/consts'
import { useInstanceStore } from '@/store/instance'
import Import from './Import/index.vue'
import Cat from './Cat/index.vue'

View File

@ -1,6 +1,6 @@
<!-- 绑定设备 -->
<template>
<a-modal
<j-modal
:maskClosable="false"
width="1100px"
:visible="true"
@ -18,7 +18,7 @@
@search="handleSearch"
type="simple"
/>
<JTable
<j-pro-table
ref="bindDeviceRef"
:columns="columns"
:request="queryUnbounded"
@ -44,14 +44,14 @@
}}
</template>
<template #state="slotProps">
<a-badge
<j-badge
:text="slotProps.state.text"
:status="statusMap.get(slotProps.state.value)"
/>
</template>
</JTable>
</j-pro-table>
</div>
</a-modal>
</j-modal>
</template>
<script setup lang="ts">

View File

@ -8,15 +8,15 @@
:data="current"
@change="saveChange"
/>
<a-row :gutter="[24, 24]">
<a-col :span="24">
<a-card>
<a-descriptions size="small" :column="3" bordered>
<j-row :gutter="[24, 24]">
<j-col :span="24">
<j-card>
<j-descriptions size="small" :column="3" bordered>
<template #title>
<Guide>
<template #title>
<span>基本信息</span>
<a-button
<j-button
type="link"
@click="
() => {
@ -28,74 +28,74 @@
>
<AIcon type="EditOutlined"></AIcon>
编辑
</a-button>
</j-button>
</template>
</Guide>
</template>
<a-descriptions-item label="卡号">{{
<j-descriptions-item label="卡号">{{
detail.id
}}</a-descriptions-item>
<a-descriptions-item label="ICCID">{{
}}</j-descriptions-item>
<j-descriptions-item label="ICCID">{{
detail.iccId
}}</a-descriptions-item>
<a-descriptions-item label="绑定设备">{{
}}</j-descriptions-item>
<j-descriptions-item label="绑定设备">{{
detail.deviceName
}}</a-descriptions-item>
<a-descriptions-item label="平台类型">{{
}}</j-descriptions-item>
<j-descriptions-item label="平台类型">{{
detail.operatorPlatformType?.text
}}</a-descriptions-item>
<a-descriptions-item label="平台名称">{{
}}</j-descriptions-item>
<j-descriptions-item label="平台名称">{{
detail.platformConfigName
}}</a-descriptions-item>
<a-descriptions-item label="运营商">{{
}}</j-descriptions-item>
<j-descriptions-item label="运营商">{{
detail.operatorName
}}</a-descriptions-item>
<a-descriptions-item label="类型">{{
}}</j-descriptions-item>
<j-descriptions-item label="类型">{{
detail.cardType?.text
}}</a-descriptions-item>
<a-descriptions-item label="激活日期">{{
}}</j-descriptions-item>
<j-descriptions-item label="激活日期">{{
detail.activationDate
? moment(detail.activationDate).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}</a-descriptions-item>
<a-descriptions-item label="更新时间">{{
}}</j-descriptions-item>
<j-descriptions-item label="更新时间">{{
detail.updateTime
? moment(detail.updateTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}</a-descriptions-item>
<a-descriptions-item label="总流量">{{
}}</j-descriptions-item>
<j-descriptions-item label="总流量">{{
detail.totalFlow
? detail.totalFlow.toFixed(2) + ' M'
: ''
}}</a-descriptions-item>
<a-descriptions-item label="使用流量">{{
}}</j-descriptions-item>
<j-descriptions-item label="使用流量">{{
detail.usedFlow
? detail.usedFlow.toFixed(2) + ' M'
: ''
}}</a-descriptions-item>
<a-descriptions-item label="剩余流量">{{
}}</j-descriptions-item>
<j-descriptions-item label="剩余流量">{{
detail.residualFlow
? detail.residualFlow.toFixed(2) + ' M'
: ''
}}</a-descriptions-item>
<a-descriptions-item label="状态">{{
}}</j-descriptions-item>
<j-descriptions-item label="状态">{{
detail?.cardState?.text
}}</a-descriptions-item>
<a-descriptions-item label="说明">{{
}}</j-descriptions-item>
<j-descriptions-item label="说明">{{
detail?.describe
}}</a-descriptions-item>
</a-descriptions>
</a-card>
</a-col>
<a-col :span="24">
}}</j-descriptions-item>
</j-descriptions>
</j-card>
</j-col>
<j-col :span="24">
<!-- 流量统计 -->
<a-row :gutter="24">
<a-col :span="16">
<j-row :gutter="24">
<j-col :span="16">
<div class="card">
<Guide title="流量统计">
<template #extra>
@ -113,15 +113,15 @@
:chartData="flowData"
/>
</div>
</a-col>
<a-col :span="8">
</j-col>
<j-col :span="8">
<div class="card">
<Guide title="数据统计" />
<div class="static-info" style="min-height: 490px">
<div class="data-statistics-item">
<div class="flow-info" style="width: 100%">
<div class="label">昨日流量消耗</div>
<a-tooltip placement="bottomLeft">
<j-tooltip placement="bottomLeft">
<template #title>
<span>{{ dayTotal }} M</span>
</template>
@ -129,7 +129,7 @@
{{ dayTotal }}
<span class="unit">M</span>
</div>
</a-tooltip>
</j-tooltip>
</div>
<LineChart
color="#FBA500"
@ -139,7 +139,7 @@
<div class="data-statistics-item">
<div class="flow-info" style="width: 100%">
<div class="label">当月流量消耗</div>
<a-tooltip placement="bottomLeft">
<j-tooltip placement="bottomLeft">
<template #title>
<span>{{ monthTotal }} M</span>
</template>
@ -147,14 +147,14 @@
{{ monthTotal }}
<span class="unit">M</span>
</div>
</a-tooltip>
</j-tooltip>
</div>
<LineChart :chartData="monthOptions" />
</div>
<div class="data-statistics-item">
<div class="flow-info" style="width: 100%">
<div class="label">本年流量消耗</div>
<a-tooltip placement="bottomLeft">
<j-tooltip placement="bottomLeft">
<template #title>
<span>{{ yearTotal }} M</span>
</template>
@ -162,7 +162,7 @@
{{ yearTotal }}
<span class="unit">M</span>
</div>
</a-tooltip>
</j-tooltip>
</div>
<LineChart
color="#58E1D3"
@ -171,10 +171,10 @@
</div>
</div>
</div>
</a-col>
</a-row>
</a-col>
</a-row>
</j-col>
</j-row>
</j-col>
</j-row>
</page-container>
</template>

View File

@ -1,6 +1,6 @@
<template>
<!-- 导入 -->
<a-modal
<j-modal
:maskClosable="false"
:visible="true"
title="导入"
@ -10,30 +10,30 @@
@cancel="handleCancel"
>
<div style="margin-top: 10px">
<a-form :layout="'vertical'">
<a-form-item label="平台对接" required>
<a-select
<j-form :layout="'vertical'">
<j-form-item label="平台对接" required>
<j-select
showSearch
v-model:value="modelRef.configId"
:options="configList"
placeholder="请选择平台对接"
>
</a-select>
</a-form-item>
</j-select>
</j-form-item>
<a-form-item v-if="modelRef.configId" label="文件格式">
<a-radio-group
<j-form-item v-if="modelRef.configId" label="文件格式">
<j-radio-group
button-style="solid"
v-model:value="modelRef.fileType"
placeholder="请选择文件格式"
>
<a-radio-button value="xlsx">xlsx</a-radio-button>
<a-radio-button value="csv">csv</a-radio-button>
</a-radio-group>
</a-form-item>
<j-radio-button value="xlsx">xlsx</j-radio-button>
<j-radio-button value="csv">csv</j-radio-button>
</j-radio-group>
</j-form-item>
<a-form-item label="文件上传" v-if="modelRef.configId">
<a-upload
<j-form-item label="文件上传" v-if="modelRef.configId">
<j-upload
v-model:fileList="modelRef.upload"
name="file"
:action="FILE_UPLOAD"
@ -44,24 +44,24 @@
:showUploadList="false"
@change="fileChange"
>
<a-button :loading="loading">
<j-button :loading="loading">
<template #icon>
<AIcon type="UploadOutlined" />
</template>
文件上传
</a-button>
</a-upload>
</a-form-item>
<a-form-item v-if="modelRef.configId" label="下载模板">
<a-space>
<a-button icon="file" @click="downFileFn('xlsx')">
</j-button>
</j-upload>
</j-form-item>
<j-form-item v-if="modelRef.configId" label="下载模板">
<j-space>
<j-button icon="file" @click="downFileFn('xlsx')">
.xlsx
</a-button>
<a-button icon="file" @click="downFileFn('csv')">
</j-button>
<j-button icon="file" @click="downFileFn('csv')">
.csv
</a-button>
</a-space>
</a-form-item>
</j-button>
</j-space>
</j-form-item>
<div v-if="totalCount">
<a-icon class="check-num" type="check" /> 已完成 总数量
<span class="check-num">{{ totalCount }}</span>
@ -71,9 +71,9 @@
失败 总数量
<span class="check-num">{{ errCount }}</span>
</div>
</a-form>
</j-form>
</div>
</a-modal>
</j-modal>
</template>
<script setup lang="ts">

View File

@ -1,5 +1,5 @@
<template>
<a-modal
<j-modal
:maskClosable="false"
width="600px"
:visible="true"
@ -11,39 +11,39 @@
:confirmLoading="btnLoading"
>
<div style="margin-top: 10px">
<a-form
<j-form
:layout="'vertical'"
ref="formRef"
:rules="rules"
:model="modelRef"
>
<a-form-item label="卡号" name="id">
<a-input
<j-form-item label="卡号" name="id">
<j-input
v-model:value="modelRef.id"
placeholder="请输入卡号"
:disabled="type === 'edit'"
></a-input>
</a-form-item>
<a-form-item name="iccId">
></j-input>
</j-form-item>
<j-form-item name="iccId">
<template #label>
<span>
ICCID
<a-tooltip title="IC卡的唯一识别号码">
<j-tooltip title="IC卡的唯一识别号码">
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-input
<j-input
v-model:value="modelRef.iccId"
placeholder="请输入ICCID"
:disabled="type === 'edit'"
/>
</a-form-item>
<a-form-item label="平台对接" name="platformConfigId">
<a-select
</j-form-item>
<j-form-item label="平台对接" name="platformConfigId">
<j-select
showSearch
:filter-option="filterOption"
:disabled="type === 'edit'"
@ -52,11 +52,11 @@
v-model:value="modelRef.platformConfigId"
placeholder="请选择平台对接"
>
</a-select>
</a-form-item>
</j-select>
</j-form-item>
<a-form-item label="运营商" name="operatorName">
<a-select
<j-form-item label="运营商" name="operatorName">
<j-select
allowClear
showSearch
:filter-option="filterOption"
@ -64,10 +64,10 @@
v-model:value="modelRef.operatorName"
placeholder="请选择运营商"
>
</a-select>
</a-form-item>
<a-form-item label="类型" name="cardType">
<a-select
</j-select>
</j-form-item>
<j-form-item label="类型" name="cardType">
<j-select
allowClear
showSearch
:disabled="type === 'edit'"
@ -76,19 +76,19 @@
v-model:value="modelRef.cardType"
placeholder="请选择类型"
>
</a-select>
</a-form-item>
<a-form-item label="说明" name="describe">
<a-textarea
</j-select>
</j-form-item>
<j-form-item label="说明" name="describe">
<j-textarea
v-model:value="modelRef.describe"
placeholder="请输入说明"
showCount
:maxlength="200"
/>
</a-form-item>
</a-form>
</j-form-item>
</j-form>
</div>
</a-modal>
</j-modal>
</template>
<script lang="ts" setup>

View File

@ -2,37 +2,40 @@
<template>
<page-container>
<Search :columns="columns" target="iot-card-management-search" @search="handleSearch" />
<JTable ref="cardManageRef" :columns="columns" :request="query"
<j-pro-table ref="cardManageRef" :columns="columns" :request="query"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }" :rowSelection="{
selectedRowKeys: _selectedRowKeys,
onChange: onSelectChange,
}" @cancelSelect="cancelSelect" :params="params" :gridColumn="3">
<template #headerTitle>
<a-space>
<a-button type="primary" @click="handleAdd">
<j-space>
<!-- <a-button type="primary" @click="handleAdd">
<AIcon type="PlusOutlined" />新增
</a-button>
<a-dropdown>
<a-button>
</a-button> -->
<PermissionButton @click="handleAdd" :hasPermission="'iot-card/CardManagement:add'" type="primary">
<AIcon type="PlusOutlined" />新增
</PermissionButton>
<j-dropdown>
<j-button>
批量操作
<AIcon type="DownOutlined" />
</a-button>
</j-button>
<template #overlay>
<a-menu>
<a-menu-item>
<j-menu>
<j-menu-item>
<PermissionButton @click="exportVisible = true"
:hasPermission="'iot-card/CardManagement:export'">
<AIcon type="ExportOutlined" />
批量导出
</PermissionButton>
</a-menu-item>
<a-menu-item>
</j-menu-item>
<j-menu-item>
<PermissionButton @click="importVisible = true"
:hasPermission="'iot-card/CardManagement:import'">
<AIcon type="ImportOutlined" />批量导入
</PermissionButton>
</a-menu-item>
<a-menu-item>
</j-menu-item>
<j-menu-item>
<PermissionButton :popConfirm="{
title: '确认激活吗?',
onConfirm: handleActive,
@ -40,8 +43,8 @@
<AIcon type="CheckCircleOutlined" />
批量激活
</PermissionButton>
</a-menu-item>
<a-menu-item>
</j-menu-item>
<j-menu-item>
<PermissionButton :popConfirm="{
title: '确认停用吗?',
onConfirm: handleStop,
@ -49,8 +52,8 @@
<AIcon type="StopOutlined" />
批量停用
</PermissionButton>
</a-menu-item>
<a-menu-item>
</j-menu-item>
<j-menu-item>
<PermissionButton :popConfirm="{
title: '确认复机吗?',
onConfirm: handleResumption,
@ -58,8 +61,8 @@
<AIcon type="PoweroffOutlined" />
批量复机
</PermissionButton>
</a-menu-item>
<a-menu-item>
</j-menu-item>
<j-menu-item>
<PermissionButton :popConfirm="{
title: '确认同步状态吗?',
onConfirm: handleSync,
@ -67,8 +70,8 @@
<AIcon type="SwapOutlined" />
同步状态
</PermissionButton>
</a-menu-item>
<a-menu-item v-if="_selectedRowKeys.length > 0">
</j-menu-item>
<j-menu-item v-if="_selectedRowKeys.length > 0">
<PermissionButton :popConfirm="{
title: '确认删除吗?',
onConfirm: handelRemove,
@ -76,11 +79,11 @@
<AIcon type="SwapOutlined" />
批量删除
</PermissionButton>
</a-menu-item>
</a-menu>
</j-menu-item>
</j-menu>
</template>
</a-dropdown>
</a-space>
</j-dropdown>
</j-space>
</template>
<template #card="slotProps">
<CardBox :value="slotProps" @click="handleClick" :actions="getActions(slotProps, 'card')" v-bind="slotProps"
@ -99,23 +102,23 @@
<h3 class="card-item-content-title">
{{ slotProps.id }}
</h3>
<a-row>
<a-col :span="8">
<j-row>
<j-col :span="8">
<div class="card-item-content-text">
平台对接
</div>
<div>{{ slotProps.platformConfigName }}</div>
</a-col>
<a-col :span="6">
</j-col>
<j-col :span="6">
<div class="card-item-content-text">类型</div>
<div>{{ slotProps.cardType.text }}</div>
</a-col>
<a-col :span="6">
</j-col>
<j-col :span="6">
<div class="card-item-content-text">提醒</div>
<!-- <div>{{ slotProps.cardType.text }}</div> -->
</a-col>
</a-row>
<a-divider style="margin: 12px 0" />
</j-col>
</j-row>
<j-divider style="margin: 12px 0" />
<div v-if="slotProps.usedFlow === 0">
<span class="flow-text">
{{ slotProps.totalFlow }}
@ -135,7 +138,7 @@
总共 {{ slotProps.totalFlow }} M
</div>
</div>
<a-progress :strokeColor="'#ADC6FF'" :showInfo="false" :percent="
<j-progress :strokeColor="'#ADC6FF'" :showInfo="false" :percent="
slotProps.totalFlow - slotProps.usedFlow
" />
</div>
@ -185,7 +188,7 @@
</template>
</a-button>
</template>
</a-tooltip> -->
</j-tooltip> -->
</template>
</CardBox>
</template>
@ -244,7 +247,7 @@
}}
</template>
<template #action="slotProps">
<a-space :size="16">
<j-space :size="16">
<template
v-for="i in getActions(slotProps,'table')"
:key="i.key"
@ -263,9 +266,9 @@
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</a-space>
</j-space>
</template>
</JTable>
</j-pro-table>
<!-- 批量导入 -->
<Import v-if="importVisible" @close="importVisible = false" />
<!-- 批量导出 -->

View File

@ -2,14 +2,14 @@
<template>
<page-container>
<div class="card-dashboard-container">
<a-card style="margin-bottom: 24px">
<a-row :gutter="24">
<a-col :span="24"><Guide title="数据统计" /></a-col>
<a-col :span="8">
<j-card style="margin-bottom: 24px">
<j-row :gutter="24">
<j-col :span="24"><Guide title="数据统计" /></j-col>
<j-col :span="8">
<div class="data-statistics-item">
<div class="flow-info" style="width: 100%">
<div class="label">昨日流量消耗</div>
<a-tooltip placement="bottomLeft">
<j-tooltip placement="bottomLeft">
<template #title>
<span>{{ dayTotal }} M</span>
</template>
@ -17,19 +17,19 @@
{{ dayTotal }}
<span class="unit">M</span>
</div>
</a-tooltip>
</j-tooltip>
</div>
<LineChart
color="#FBA500"
:chartData="dayOptions"
/>
</div>
</a-col>
<a-col :span="8">
</j-col>
<j-col :span="8">
<div class="data-statistics-item">
<div class="flow-info" style="width: 100%">
<div class="label">当月流量消耗</div>
<a-tooltip placement="bottomLeft">
<j-tooltip placement="bottomLeft">
<template #title>
<span>{{ monthTotal }} M</span>
</template>
@ -37,16 +37,16 @@
{{ monthTotal }}
<span class="unit">M</span>
</div>
</a-tooltip>
</j-tooltip>
</div>
<LineChart :chartData="monthOptions" />
</div>
</a-col>
<a-col :span="8">
</j-col>
<j-col :span="8">
<div class="data-statistics-item">
<div class="flow-info" style="width: 100%">
<div class="label">本年流量消耗</div>
<a-tooltip placement="bottomLeft">
<j-tooltip placement="bottomLeft">
<template #title>
<span>{{ yearTotal }} M</span>
</template>
@ -54,18 +54,18 @@
{{ yearTotal }}
<span class="unit">M</span>
</div>
</a-tooltip>
</j-tooltip>
</div>
<LineChart
color="#58E1D3"
:chartData="yearOptions"
/>
</div>
</a-col>
</a-row>
</a-card>
<a-row :gutter="24">
<a-col :span="16">
</j-col>
</j-row>
</j-card>
<j-row :gutter="24">
<j-col :span="16">
<div class="static-card">
<Guide title="流量统计">
<template #extra>
@ -85,11 +85,11 @@
:chartData="flowData"
/>
<div class="empty-body" v-else>
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" />
<j-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
</a-col>
<a-col :span="8">
</j-col>
<j-col :span="8">
<div class="static-card">
<Guide title="流量使用TOP10">
<template #extra>
@ -118,7 +118,7 @@
</div>
<div class="cardNum">{{ item.cardNum }}</div>
<div class="progress">
<a-progress
<j-progress
:strokeColor="'#ADC6FF'"
:trailColor="'#E0E4E8'"
:strokeLinecap="'butt'"
@ -128,7 +128,7 @@
(item.value / topTotal) * 100,
)
"
></a-progress>
></j-progress>
</div>
<div class="total">
{{ item?.value?.toFixed(2) }} M
@ -136,11 +136,11 @@
</div>
</div>
<div class="empty-body" v-else>
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" />
<j-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
</a-col>
</a-row>
</j-col>
</j-row>
</div>
</page-container>
</template>

View File

@ -1,8 +1,8 @@
<!-- 物联卡-首页 -->
<template>
<page-container>
<a-row :gutter="24">
<a-col :span="14">
<j-row :gutter="24">
<j-col :span="14">
<div class="home-guide">
<Guide title="物联卡引导"></Guide>
<div
@ -25,8 +25,8 @@
</div>
</div>
</div>
</a-col>
<a-col :span="10">
</j-col>
<j-col :span="10">
<div class="home-statistics">
<Guide title="基础统计">
<template #extra>
@ -71,8 +71,8 @@
</div>
</div>
</div>
</a-col>
<a-col :span="24" style="min-height: 580px">
</j-col>
<j-col :span="24" style="min-height: 580px">
<div class="home-body">
<Guide
title="平台架构图"
@ -82,22 +82,20 @@
<img :src="getImage('/iot-card/iotcard-home.png')" />
</div>
</div>
</a-col>
</a-row>
</j-col>
</j-row>
</page-container>
</template>
<script setup lang="ts">
import { getImage } from '@/utils/comm';
import Guide from '../components/Guide.vue';
import { message } from 'ant-design-vue';
import moment from 'moment';
import { queryFlow, list } from '@/api/iot-card/home';
import * as echarts from 'echarts';
import { useMenuStore } from '@/store/menu';
import { usePermissionStore } from '@/store/permission';
const router = useRouter();
const { proxy } = <any>getCurrentInstance();
interface GuideItemProps {

View File

@ -1,16 +1,16 @@
<template>
<page-container>
<a-card>
<a-row :gutter="24">
<a-col :span="14">
<j-card>
<j-row :gutter="24">
<j-col :span="14">
<TitleComponent data="详情" />
<a-form
<j-form
:layout="'vertical'"
ref="formRef"
:rules="rules"
:model="form"
>
<a-form-item
<j-form-item
label="平台类型"
name="operatorName"
required
@ -28,104 +28,104 @@
v-model:value="form.operatorName"
@change="typeChange"
></PlatformType
></a-form-item>
<a-form-item label="名称" name="name">
<a-input
></j-form-item>
<j-form-item label="名称" name="name">
<j-input
v-model:value="form.name"
placeholder="请输入名称"
/>
</a-form-item>
</j-form-item>
<!-- onelink -->
<div v-if="form.operatorName === 'onelink'">
<a-form-item label="App ID" name="appId">
<a-input
<j-form-item label="App ID" name="appId">
<j-input
v-model:value="form.appId"
placeholder="请输入App ID"
/>
</a-form-item>
<a-form-item label="Password" name="passWord">
<a-input-password
</j-form-item>
<j-form-item label="Password" name="passWord">
<j-input-password
v-model:value="form.passWord"
placeholder="请输入密码"
/>
</a-form-item>
<a-form-item label="接口地址" name="apiAddr">
<a-input
</j-form-item>
<j-form-item label="接口地址" name="apiAddr">
<j-input
v-model:value="form.apiAddr"
placeholder="请输入接口地址"
/>
</a-form-item>
</j-form-item>
</div>
<!-- ctwing -->
<div v-if="form.operatorName === 'ctwing'">
<a-form-item label="用户id" name="userId">
<a-input
<j-form-item label="用户id" name="userId">
<j-input
v-model:value="form.userId"
placeholder="请输入用户id"
/>
</a-form-item>
<a-form-item label="密码" name="passWord">
<a-input-password
</j-form-item>
<j-form-item label="密码" name="passWord">
<j-input-password
v-model:value="form.passWord"
placeholder="请输入密码"
/>
</a-form-item>
<a-form-item label="secretKey" name="secretKey">
<a-input
</j-form-item>
<j-form-item label="secretKey" name="secretKey">
<j-input
v-model:value="form.secretKey"
placeholder="请输入secretKey"
/>
</a-form-item>
</j-form-item>
</div>
<!-- unicom -->
<div v-if="form.operatorName === 'unicom'">
<a-form-item label="App ID" name="appId">
<a-input
<j-form-item label="App ID" name="appId">
<j-input
v-model:value="form.appId"
placeholder="请输入App ID"
/>
</a-form-item>
<a-form-item label="App Secret" name="appSecret">
<a-input
</j-form-item>
<j-form-item label="App Secret" name="appSecret">
<j-input
v-model:value="form.appSecret"
placeholder="请输入App Secret"
/>
</a-form-item>
<a-form-item label="创建者ID" name="openId">
<a-input
</j-form-item>
<j-form-item label="创建者ID" name="openId">
<j-input
v-model:value="form.openId"
placeholder="请输入创建者ID"
/>
</a-form-item>
</j-form-item>
</div>
<a-form-item label="说明" name="explain">
<a-textarea
<j-form-item label="说明" name="explain">
<j-textarea
v-model:value="form.explain"
placeholder="请输入说明"
showCount
:rows="3"
:maxlength="200"
/>
</a-form-item>
<a-form-item>
<a-divider />
<a-button
</j-form-item>
<j-form-item>
<j-divider />
<j-button
:loading="saveBtnLoading"
type="primary"
@click="handleSave"
>
保存
</a-button>
</a-form-item>
</a-form>
</a-col>
<a-col :span="10">
</j-button>
</j-form-item>
</j-form>
</j-col>
<j-col :span="10">
<Doc :type="form.operatorName" />
</a-col>
</a-row>
</a-card>
</j-col>
</j-row>
</j-card>
</page-container>
</template>

View File

@ -24,7 +24,7 @@
获取路径中移物联卡能力开放平台--个人中心--客户信息--接入信息
</p>
<div class="image">
<a-image
<j-image
width="100%"
:src="getImage('/iot-card/onelink-appid.png')"
/>
@ -37,7 +37,7 @@
获取路径中移物联卡能力开放平台--个人中心--客户信息--接入信息
</p>
<div class="image">
<a-image
<j-image
width="100%"
:src="getImage('/iot-card/onelink-pass.png')"
/>
@ -77,7 +77,7 @@
获取路径5G连接管理平台--能力开放--API网关账号管理
</p>
<div class="image">
<a-image
<j-image
width="100%"
:src="getImage('/iot-card/ctwing-id.png')"
/>
@ -90,7 +90,7 @@
获取路径5G连接管理平台--能力开放--API网关账号管理
</p>
<div class="image">
<a-image
<j-image
width="100%"
:src="getImage('/iot-card/ctwing-pass.png')"
/>
@ -103,7 +103,7 @@
获取路径5G连接管理平台--能力开放--API网关账号管理
</p>
<div class="image">
<a-image
<j-image
width="100%"
:src="getImage('/iot-card/ctwing-secret.png')"
/>
@ -137,7 +137,7 @@
获取路径雁飞智连CMP平台--我的应用--应用列表
</p>
<div class="image">
<a-image
<j-image
width="100%"
:src="getImage('/iot-card/unicom-id.png')"
/>
@ -150,7 +150,7 @@
获取路径雁飞智连CMP平台--我的应用--应用列表
</p>
<div class="image">
<a-image
<j-image
width="100%"
:src="getImage('/iot-card/unicom-secret.png')"
/>
@ -164,7 +164,7 @@
<br />
</p>
<div class="image">
<a-image
<j-image
width="100%"
:src="getImage('/iot-card/unicom-openid.png')"
/>

View File

@ -6,7 +6,7 @@
target="platform-search"
@search="handleSearch"
/>
<JTable
<j-pro-table
ref="platformRef"
:columns="columns"
:request="queryList"
@ -15,11 +15,14 @@
:gridColumn="3"
>
<template #headerTitle>
<a-space>
<a-button type="primary" @click="handleAdd">
<j-space>
<!-- <j-button type="primary" @click="handleAdd">
<AIcon type="PlusOutlined" />新增
</a-button>
</a-space>
</j-button> -->
<PermissionButton @click="handleAdd" :hasPermission="'iot-card/Platform:add'" type="primary">
<AIcon type="PlusOutlined" />新增
</PermissionButton>
</j-space>
</template>
<template #card="slotProps">
<CardBox
@ -42,18 +45,18 @@
<h3 class="card-item-content-title">
{{ slotProps.name }}
</h3>
<a-row>
<a-col :span="12">
<j-row>
<j-col :span="12">
<div class="card-item-content-text">
平台类型
</div>
<div>{{ slotProps.operatorName }}</div>
</a-col>
<a-col :span="12">
</j-col>
<j-col :span="12">
<div class="card-item-content-text">说明</div>
<div>{{ slotProps.explain }}</div>
</a-col>
</a-row>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton :disabled="item.disabled" :popConfirm="item.popConfirm" :tooltip="{
@ -69,7 +72,7 @@
</CardBox>
</template>
<template #state="slotProps">
<a-badge
<j-badge
:text="slotProps.state.text"
:status="
slotProps.state.value === 'disabled'
@ -79,7 +82,7 @@
/>
</template>
<template #action="slotProps">
<a-space :size="16">
<j-space :size="16">
<template
v-for="i in getActions(slotProps,'table')"
:key="i.key"
@ -98,9 +101,9 @@
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</a-space>
</j-space>
</template>
</JTable>
</j-pro-table>
</page-container>
</template>

View File

@ -1,5 +1,5 @@
<template>
<a-modal
<j-modal
:maskClosable="false"
width="1000px"
:visible="true"
@ -10,40 +10,40 @@
@cancel="handleCancel"
>
<div style="margin-top: 10px">
<a-descriptions
<j-descriptions
:column="2"
bordered
:contentStyle="{ minWidth: '300px' }"
:labelStyle="{ minWidth: '120px' }"
>
<a-descriptions-item label="充值金额">{{
<j-descriptions-item label="充值金额">{{
data.chargeMoney
}}</a-descriptions-item>
<a-descriptions-item label="账户id">{{
}}</j-descriptions-item>
<j-descriptions-item label="账户id">{{
data?.rechargeId
}}</a-descriptions-item>
<a-descriptions-item label="平台对接">{{
}}</j-descriptions-item>
<j-descriptions-item label="平台对接">{{
data.configName
}}</a-descriptions-item>
<a-descriptions-item label="订单号">{{
}}</j-descriptions-item>
<j-descriptions-item label="订单号">{{
data.orderNumber
}}</a-descriptions-item>
<a-descriptions-item label="支付方式">{{
}}</j-descriptions-item>
<j-descriptions-item label="支付方式">{{
data.paymentType
}}</a-descriptions-item>
<a-descriptions-item label="支付URL">
}}</j-descriptions-item>
<j-descriptions-item label="支付URL">
<div style="height: 100px; overflow: auto">
{{ data.url ? data.url : '' }}
</div>
</a-descriptions-item>
<a-descriptions-item label="订单时间">{{
</j-descriptions-item>
<j-descriptions-item label="订单时间">{{
data.createTime
? moment(data.createTime).format('YYYY-MM-DD HH:mm:ss')
: '-'
}}</a-descriptions-item>
</a-descriptions>
}}</j-descriptions-item>
</j-descriptions>
</div>
</a-modal>
</j-modal>
</template>
<script lang="ts" setup>

View File

@ -1,5 +1,5 @@
<template>
<a-modal
<j-modal
:maskClosable="false"
width="600px"
:visible="true"
@ -17,14 +17,14 @@
style="margin-right: 6px"
/>OneLink
</div>
<a-form
<j-form
layout="vertical"
ref="formRef"
:rules="rules"
:model="modelRef"
>
<a-form-item label="平台对接" name="configId">
<a-select
<j-form-item label="平台对接" name="configId">
<j-select
v-model:value="modelRef.configId"
:options="configList"
allowClear
@ -32,16 +32,16 @@
style="width: 100%"
placeholder="请选择平台对接"
>
</a-select>
</a-form-item>
<a-form-item label="账户id" name="rechargeId">
<a-input
</j-select>
</j-form-item>
<j-form-item label="账户id" name="rechargeId">
<j-input
v-model:value="modelRef.rechargeId"
placeholder="请输入账户id"
/>
</a-form-item>
<a-form-item label="充值金额" name="chargeMoney">
<a-input-number
</j-form-item>
<j-form-item label="充值金额" name="chargeMoney">
<j-input-number
allowClear
:precision="2"
style="width: 100%"
@ -50,20 +50,20 @@
:max="500"
placeholder="请输入1~500之间的金额"
/>
</a-form-item>
</j-form-item>
<a-form-item label="支付方式" name="paymentType">
<a-select
<j-form-item label="支付方式" name="paymentType">
<j-select
allowClear
:options="PaymentMethod"
v-model:value="modelRef.paymentType"
placeholder="请选择支付方式"
>
</a-select>
</a-form-item>
</a-form>
</j-select>
</j-form-item>
</j-form>
</div>
</a-modal>
</j-modal>
</template>
<script lang="ts" setup>

View File

@ -2,10 +2,10 @@
<template>
<page-container>
<Search :columns="columns" target="recharge-search" @search="handleSearch" />
<JTable ref="rechargeRef" :columns="columns" :request="queryRechargeList" model="TABLE"
<j-pro-table ref="rechargeRef" :columns="columns" :request="queryRechargeList" model="TABLE"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }" :params="params">
<template #headerTitle>
<a-space>
<j-space>
<PermissionButton @click="visible = true" :hasPermission="'iot-card/Recharge:pay'" type="primary">
充值
</PermissionButton>
@ -15,7 +15,7 @@
</span>
本平台仅提供充值入口具体充值结果需以运营商的充值结果为准
</div>
</a-space>
</j-space>
</template>
<template #createTime="slotProps">
{{
@ -27,7 +27,7 @@
}}
</template>
<template #action="slotProps">
<a-space :size="16">
<j-space :size="16">
<template
v-for="i in getActions(slotProps)"
:key="i.key"
@ -46,9 +46,9 @@
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</a-space>
</j-space>
</template>
</JTable>
</j-pro-table>
<!-- 充值 -->
<Save v-if="visible" @change="saveChange" />
<Detail v-if="detailVisible" :data="current" @close="close" />

View File

@ -6,7 +6,7 @@
target="record-search"
@search="handleSearch"
/>
<JTable
<j-pro-table
ref="RecordRef"
:columns="columns"
:request="queryList"
@ -21,7 +21,7 @@
: ''
}}
</template>
</JTable>
</j-pro-table>
</page-container>
</template>

View File

@ -1,21 +1,21 @@
<template>
<div>
<a-radio-group
<j-radio-group
v-if="quickBtn"
default-value="today"
button-style="solid"
v-model:value="radioValue"
@change="(e) => handleBtnChange(e.target.value)"
>
<a-radio-button
<j-radio-button
v-for="item in quickBtnList"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</a-radio-button>
</a-radio-group>
<a-range-picker
</j-radio-button>
</j-radio-group>
<j-range-picker
format="YYYY-MM-DD"
valueFormat="YYYY-MM-DD"
style="margin-left: 12px"
@ -23,7 +23,7 @@
v-model:value="rangeVal"
:allowClear="false"
>
</a-range-picker>
</j-range-picker>
</div>
</template>

View File

@ -17,7 +17,7 @@
@search="handleSearch"
/>
<JTable
<JProTable
ref="listRef"
model="table"
:columns="columns"
@ -62,7 +62,7 @@
></a-badge>
</a-space>
</template>
</JTable>
</JProTable>
</a-modal>
</template>

View File

@ -8,7 +8,7 @@
@search="handleSearch"
/>
<JTable
<JProTable
ref="listRef"
model="table"
:columns="columns"
@ -144,7 +144,7 @@
</a-tooltip>
</a-space>
</template>
</JTable>
</JProTable>
<BindChannel v-model:visible="bindVis" @submit="listRef.reload()" />
</page-container>

View File

@ -6,7 +6,7 @@
@search="handleSearch"
/>
<JTable
<JProTable
ref="listRef"
:columns="columns"
:request="(e:any) => lastValueFrom(e)"
@ -192,7 +192,7 @@
</template> -->
</a-space>
</template>
</JTable>
</JProTable>
<Publish v-model:visible="publishVis" :data="currentData" />
</page-container>

View File

@ -0,0 +1,58 @@
.media-live {
display: flex;
.live-player-tools {
flex-basis: 230px;
.direction-item {
font-size: 30px !important;
}
.zoom-item {
font-size: 20px !important;
}
}
.media-live-video {
position: relative;
flex-grow: 1;
width: 0;
.media-tool {
position: absolute;
top: 4px;
right: 0;
z-index: 2;
display: flex;
opacity: 0;
transition: opacity 0.3s;
&:hover {
opacity: 1;
}
.tool-item {
margin-right: 6px;
padding: 4px;
color: #fff;
font-size: 14px;
background-color: hsla(0, 0%, 50.2%, 0.8);
border-radius: 2px;
cursor: pointer;
&:hover {
background-color: hsla(0, 0%, 50.2%, 0.85);
}
&:active {
background-color: hsla(0, 0%, 50.2%, 0.9);
}
}
}
}
}
.media-live-tool {
display: flex;
margin-top: 24px;
}

View File

@ -0,0 +1,159 @@
<!-- 视频设备 - 播放 -->
<template>
<a-modal
v-model:visible="_vis"
title="播放"
cancelText="取消"
okText="确定"
width="800px"
:maskClosable="false"
@ok="_vis = false"
@cancel="_vis = false"
>
<div class="media-live">
<div class="media-live-video">
<div class="media-tool">
<div class="tool-item" @click.stop="handleRecord">
{{
isRecord === 0
? '开始录像'
: isRecord === 1
? '请求录像中'
: '停止录像'
}}
</div>
<div class="tool-item">刷新</div>
<div class="tool-item" @click.stop="handleReset">重置</div>
</div>
<LivePlayer :src="src" :type="mediaType" />
</div>
<MediaTool
@onMouseDown="handleMouseDown"
@onMouseUp="handleMouseUp"
/>
</div>
<div class="media-live-tool">
<a-radio-group
v-model:value="mediaType"
button-style="solid"
@change="mediaStart"
>
<a-radio-button value="mp4">MP4</a-radio-button>
<a-radio-button value="flv">FLV</a-radio-button>
<a-radio-button value="m3u8">HLS</a-radio-button>
</a-radio-group>
</div>
</a-modal>
</template>
<script setup lang="ts">
import { PropType } from 'vue';
import LivePlayer from '@/components/Player/index.vue';
import MediaTool from '@/components/Player/mediaTool.vue';
import channelApi from '@/api/media/channel';
type Emits = {
(e: 'update:visible', data: boolean): void;
};
const emit = defineEmits<Emits>();
const props = defineProps({
visible: { type: Boolean, default: false },
data: {
type: Object as PropType<Partial<Record<string, any>>>,
default: () => ({}),
},
});
const _vis = computed({
get: () => props.visible,
set: (val) => emit('update:visible', val),
});
//
const src = ref('');
//
const mediaType = ref('mp4');
/**
* 媒体开始播放
*/
const mediaStart = () => {
src.value = channelApi.ptzStart(
props.data.deviceId,
props.data.channelId,
mediaType.value,
);
};
//
const isRecord = ref(0); // 0 1 2
/**
* 查询录像状态
*/
const getIsRecord = async () => {
const { result } = await channelApi.ptzIsRecord(
props.data.deviceId,
props.data.channelId,
);
isRecord.value = result ? 2 : 0;
};
/**
* 点击录像/停止录像
*/
const handleRecord = async () => {
if (isRecord.value === 0) {
isRecord.value = 1;
const res = await channelApi.recordStart(
props.data.deviceId,
props.data.channelId,
{ local: false },
);
if (res.success) {
isRecord.value = 2;
} else {
isRecord.value = 0;
}
} else if (isRecord.value === 2) {
const res = await channelApi.recordStop(
props.data.deviceId,
props.data.channelId,
{ local: false },
);
if (res.success) {
isRecord.value = 0;
}
}
};
/**
* 重置
*/
const handleReset = async () => {
channelApi.mediaStop(props.data.deviceId, props.data.channelId);
};
/**
* 点击控制按钮
* @param type 控制类型
*/
const handleMouseDown = (type: string) => {
channelApi.ptzTool(props.data.deviceId, props.data.channelId, type);
};
const handleMouseUp = () => {
channelApi.ptzStop(props.data.deviceId, props.data.channelId);
};
watch(
() => _vis.value,
(val: boolean) => {
if (val) {
mediaStart();
getIsRecord();
}
},
);
</script>
<style lang="less" scoped>
@import './index.less';
</style>

View File

@ -8,7 +8,7 @@
@search="handleSearch"
/>
<JTable
<JProTable
ref="listRef"
:columns="columns"
:request="(e:any) => ChannelApi.list(e, route?.query.id as string)"
@ -76,13 +76,14 @@
</a-tooltip>
</a-space>
</template>
</JTable>
</JProTable>
<Save
v-model:visible="saveVis"
:channelData="channelData"
@submit="listRef.reload()"
/>
<Live v-model:visible="playerVis" :data="channelData" />
</page-container>
</template>
@ -92,6 +93,7 @@ import type { ActionsType } from '@/components/Table/index.vue';
import { useMenuStore } from 'store/menu';
import { message } from 'ant-design-vue';
import Save from './Save.vue';
import Live from './Live/index.vue';
import { cloneDeep } from 'lodash-es';
const menuStory = useMenuStore();
@ -169,7 +171,7 @@ const handleAdd = () => {
};
const listRef = ref();
const playVis = ref(false);
const playerVis = ref(false);
const channelData = ref();
/**
@ -203,7 +205,8 @@ const getActions = (
},
icon: 'VideoCameraOutlined',
onClick: () => {
playVis.value = true;
channelData.value = cloneDeep(data);
playerVis.value = true;
},
},
{

View File

@ -5,7 +5,7 @@
target="notice-config"
@search="handleSearch"
/>
<JTable
<JProTable
ref="listRef"
:columns="columns"
:request="DeviceApi.list"
@ -125,7 +125,7 @@
</a-tooltip>
</a-space>
</template>
</JTable>
</JProTable>
</page-container>
</template>

View File

@ -7,7 +7,7 @@
@search="handleSearch"
/>
<JTable
<JProTable
ref="instanceRef"
:columns="columns"
:request="(e:any) => configApi.getHistory(e, data.id)"
@ -42,7 +42,7 @@
@click="handleDetail(slotProps.context)"
/>
</template>
</JTable>
</JProTable>
</a-modal>
</template>

View File

@ -34,7 +34,7 @@
<a-empty v-if="!deptTreeData.length" />
</a-col>
<a-col :span="20">
<JTable
<JProTable
ref="tableRef"
:columns="columns"
:dataSource="dataSource"
@ -92,7 +92,7 @@
</a-tooltip>
</a-space>
</template>
</JTable>
</JProTable>
</a-col>
</a-row>
</a-modal>

View File

@ -5,7 +5,7 @@
target="notice-config"
@search="handleSearch"
/>
<JTable
<JProTable
ref="configRef"
:columns="columns"
:request="ConfigApi.list"
@ -163,7 +163,7 @@
</a-tooltip>
</a-space>
</template>
</JTable>
</JProTable>
<Debug v-model:visible="debugVis" :data="currentConfig" />
<Log v-model:visible="logVis" :data="currentConfig" />

View File

@ -7,7 +7,7 @@
@search="handleSearch"
/>
<JTable
<JProTable
ref="instanceRef"
:columns="columns"
:request="(e:any) => templateApi.getHistory(e, data.id)"
@ -42,7 +42,7 @@
@click="handleDetail(slotProps.context)"
/>
</template>
</JTable>
</JProTable>
</a-modal>
</template>

View File

@ -5,7 +5,7 @@
target="notice-config"
@search="handleSearch"
/>
<JTable
<JProTable
ref="configRef"
:columns="columns"
:request="TemplateApi.list"
@ -159,7 +159,7 @@
</a-tooltip>
</a-space>
</template>
</JTable>
</JProTable>
<Debug v-model:visible="debugVis" :data="currentConfig" />
<Log v-model:visible="logVis" :data="currentConfig" />

View File

@ -61,10 +61,13 @@ import { getImage } from '@/utils/comm';
import { message } from 'ant-design-vue';
import { useMenuStore } from '@/store/menu';
import { useRoute } from 'vue-router';
import { Store } from 'jetlinks-store';
import { useAlarmConfigurationStore } from '@/store/alarm';
import { storeToRefs } from 'pinia';
const route = useRoute();
const id = route.query?.id;
let selectDisable = ref(false);
const alarmConfigurationStore = useAlarmConfigurationStore();
let { configurationData } = storeToRefs(alarmConfigurationStore);
const queryData = () => {
if (id) {
detail(id).then((res) => {
@ -73,7 +76,7 @@ const queryData = () => {
form.name = res?.result?.name;
form.targetType = res?.result?.targetType;
form.description = res?.result?.description;
Store.set('configuration-data', res.result);
configurationData.value.current = res.result;
query({
terms: [
{
@ -183,7 +186,7 @@ const handleSave = async () => {
{ id: res.result?.id },
);
if (!id) {
Store.set('configuration-data', res.result);
configurationData.value.current = res.result;
}
}
})

View File

@ -0,0 +1,14 @@
<template>
<div>
<TabComponent type="detail" :id="id"/>
</div>
</template>
<script lang="ts" setup>
import TabComponent from '@/views/rule-engine/Alarm/Log/TabComponent/indev.vue'
import { useRoute } from 'vue-router';
const route = useRoute();
const id = route.query?.id
</script>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,233 @@
<template>
<a-modal
visible
title="新增"
okText="确定"
cancelText="取消"
:width="1000"
@cancel="closeModal"
@ok="saveCorrelation"
>
<Search :columns="columns" @search="handleSearch"></Search>
<div style="height: 500px; overflow-y: auto">
<JTable
model="CARD"
:request="query"
:rowSelection="{
selectedRowKeys: _selectedRowKeys,
}"
@cancelSelect="cancelSelect"
:gridColumns="[1, 1, 1]"
:defaultParams="{
sorts: [
{
name: 'createTime',
order: 'desc',
},
],
terms,
}"
:params="params"
>
<template #card="slotProps">
<SceneCard
:value="slotProps"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:active="_selectedRowKeys.includes(slotProps.id)"
@click="handleClick"
:statusNames="{
started: 'success',
disable: 'error',
}"
>
<template #type>
<span
><img
:height="16"
:src="
typeMap.get(slotProps.triggerType)?.icon
"
style="margin-right: 5px"
/>{{
typeMap.get(slotProps.triggerType)?.text
}}</span
>
</template>
<template #img>
<img
:src="typeMap.get(slotProps.triggerType)?.img"
/>
</template>
<template #title>
<Ellipsis style="width: calc(100% - 100px)">
<span style="font-size: 16px; font-weight: 600">
{{ slotProps.name }}
</span>
</Ellipsis>
</template>
<template #subTitle>
<Ellipsis :lineClamp="2">
说明{{
slotProps?.description ||
typeMap.get(slotProps.triggerType)?.tip
}}
</Ellipsis>
</template>
</SceneCard>
</template>
</JTable>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { query } from '@/api/rule-engine/scene';
import { bindScene } from '@/api/rule-engine/configuration';
import SceneCard from '@/views/rule-engine/Scene/SceneCard.vue';
import { getImage } from '@/utils/comm';
import { message } from 'ant-design-vue';
const columns = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
search: {
type: 'string',
},
},
{
title: '触发方式',
dataIndex: 'triggerType',
key: 'triggerType',
search: {
type: 'select',
options: [
{
label: '手动触发',
value: 'manual',
},
{
label: '定时触发',
value: 'timer',
},
{
label: '设备触发',
value: 'device',
},
],
},
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
search: {
type: 'select',
options: [
{
label: '正常',
value: 'started',
},
{
label: '禁用',
value: 'disable',
},
],
},
},
];
const props = defineProps({
id: {
type: String,
},
type: {
type: String,
},
});
const terms = [
{
terms: [
{
column: 'id',
termType: 'alarm-bind-rule$not',
value: props.id,
type: 'and',
},
{
column: 'triggerType',
termType: 'eq',
value: props.type === 'other' ? undefined : 'device',
},
],
type: 'and',
},
];
const params = ref();
const typeMap = new Map();
typeMap.set('manual', {
text: '手动触发',
img: getImage('/scene/scene-hand.png'),
icon: getImage('/scene/trigger-type-icon/manual.png'),
tip: '适用于第三方平台向物联网平台下发指令控制设备',
});
typeMap.set('timer', {
text: '定时触发',
img: getImage('/scene/scene-timer.png'),
icon: getImage('/scene/trigger-type-icon/timing.png'),
tip: '适用于定期执行固定任务',
});
typeMap.set('device', {
text: '设备触发',
img: getImage('/scene/scene-device.png'),
icon: getImage('/scene/trigger-type-icon/device.png'),
tip: '适用于设备数据或行为满足触发条件时,执行指定的动作',
});
const _selectedRowKeys = ref<string[]>([]);
const handleClick = (dt: any) => {
if (_selectedRowKeys.value.includes(dt.id)) {
const _index = _selectedRowKeys.value.findIndex((i) => i === dt.id);
_selectedRowKeys.value.splice(_index, 1);
} else {
_selectedRowKeys.value = [..._selectedRowKeys.value, dt.id];
}
console.log(_selectedRowKeys.value);
};
/**
* 取消选择事件
*/
const cancelSelect = () => {
_selectedRowKeys.value = [];
};
const log = () => {};
log();
const handleSearch = (e: any) => {
params.value = e;
};
const emit = defineEmits(['closeSave','saveScene']);
/**
* 保存选中关联场景
*/
const saveCorrelation = async () => {
if (_selectedRowKeys.value.length > 0) {
const list = _selectedRowKeys.value.map((item: any) => {
return {
alarmId: props.id,
releId: item,
};
});
const res = await bindScene([...list]);
if (res.status === 200) {
message.success('操作成功');
emit('saveScene');
}
} else {
message.error('请选择至少一条数据');
}
};
const closeModal = () => {
emit('closeSave');
};
</script>
<style lang="less" scoped>
</style>

View File

@ -1,9 +1,174 @@
<template>
<div>123</div>
<JTable
model="CARD"
:request="query"
:defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }],
terms,
}"
:gridColumns="[1, 1, 1]"
ref="actionRef"
>
<template #headerTitle>
<a-space>
<PermissionButton type="primary" @click="showModal">
<template #icon><AIcon type="PlusOutlined" /></template>
新增
</PermissionButton>
</a-space>
</template>
<template #card="slotProps">
<SceneCard
:value="slotProps"
@click="handleClick"
:actions="getActions(slotProps, 'card')"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
started: 'success',
disable: 'error',
}"
>
<template #type>
<span
><img
:height="16"
:src="typeMap.get(slotProps.triggerType)?.icon"
style="margin-right: 5px"
/>{{ typeMap.get(slotProps.triggerType)?.text }}</span
>
</template>
<template #img>
<img :src="typeMap.get(slotProps.triggerType)?.img" />
</template>
<template #title>
<Ellipsis style="width: calc(100% - 100px)">
<span style="font-size: 16px; font-weight: 600">
{{ slotProps.name }}
</span>
</Ellipsis>
</template>
<template #subTitle>
<Ellipsis :lineClamp="2">
说明{{
slotProps?.description ||
typeMap.get(slotProps.triggerType)?.tip
}}
</Ellipsis>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</PermissionButton>
</template>
</SceneCard>
</template>
</JTable>
<Save
:id="id"
:type="configurationData.current?.targetType"
@close-save="closeSave"
@save-scene="saveSuccess"
v-if="visible"
></Save>
</template>
<script lang="ts" setup>
import { query } from '@/api/rule-engine/scene';
import { unbindScene } from '@/api/rule-engine/configuration';
import { useRoute } from 'vue-router';
import SceneCard from '@/views/rule-engine/Scene/SceneCard.vue';
import type { ActionsType } from '@/components/Table';
import { getImage } from '@/utils/comm';
import { message } from 'ant-design-vue/es';
import Save from './save/index.vue';
import { useAlarmConfigurationStore } from '@/store/alarm';
import { storeToRefs } from 'pinia';
const route = useRoute();
const id = route.query?.id;
const alarmConfigurationStore = useAlarmConfigurationStore();
const { configurationData } = storeToRefs(alarmConfigurationStore);
const terms = [
{
terms: [
{
column: 'id',
termType: 'alarm-bind-rule',
value: id,
},
],
type: 'and',
},
];
const actionRef = ref();
const typeMap = new Map();
typeMap.set('manual', {
text: '手动触发',
img: getImage('/scene/scene-hand.png'),
icon: getImage('/scene/trigger-type-icon/manual.png'),
tip: '适用于第三方平台向物联网平台下发指令控制设备',
});
typeMap.set('timer', {
text: '定时触发',
img: getImage('/scene/scene-timer.png'),
icon: getImage('/scene/trigger-type-icon/timing.png'),
tip: '适用于定期执行固定任务',
});
typeMap.set('device', {
text: '设备触发',
img: getImage('/scene/scene-device.png'),
icon: getImage('/scene/trigger-type-icon/device.png'),
tip: '适用于设备数据或行为满足触发条件时,执行指定的动作',
});
const getActions = (
data: Partial<Record<string, any>>,
type: 'card' | 'table',
): ActionsType[] => {
if (!data) return [];
const actions: ActionsType[] = [
{
key: 'unbind',
text: '解绑',
icon: 'DisconnectOutlined',
popConfirm: {
title: '确定解绑?',
onConfirm: async () => {
const res = await unbindScene(id, [data.id]);
if (res.status === 200) {
message.success('操作成功');
actionRef.value.reload();
}
},
},
},
];
return actions;
};
const visible = ref(false);
const log = () => {
console.log();
};
log();
const showModal = () => {
visible.value = true;
};
const closeSave = () => {
visible.value = false;
};
const saveSuccess = () =>{
visible.value = false;
actionRef.value.reload();
}
</script>
<style lang="less" scoped>
</style>

View File

@ -8,7 +8,9 @@
<a-tab-pane key="2" tab="关联场景联动">
<Scene></Scene>
</a-tab-pane>
<a-tab-pane key="3" tab="告警记录"></a-tab-pane>
<a-tab-pane key="3" tab="告警记录">
<Log/>
</a-tab-pane>
</a-tabs>
</a-card>
</page-container>
@ -17,7 +19,8 @@
<script lang="ts" setup>
import Base from './Base/index.vue';
import Scene from './Scene/index.vue'
const activeKey = ref('2');
import Log from './Log/indev.vue'
const activeKey = ref('1');
</script>
<style lang="less" scoped>
</style>

View File

@ -280,11 +280,6 @@ let params = ref({
sorts: [{ name: 'alarmTime', order: 'desc' }],
terms: [],
});
let param = reactive({
pageSize: 10,
terms: [],
});
const handleSearch = async (params: any) => {
const resp = await query(params);
if (resp.status === 200) {

View File

@ -14,6 +14,7 @@
<Title :options='data.options.trigger' />
</AddButton>
</a-form-item>
<Action />
<AddModel v-if='visible' @cancel='visible = false' @save='save' :value='data.trigger.device' :options='data.options.trigger' />
</div>
</template>
@ -24,6 +25,7 @@ import { useSceneStore } from '@/store/scene'
import AddModel from './AddModal.vue'
import AddButton from '../components/AddButton.vue'
import Title from '../components/Title.vue'
import Action from '../action/index.vue'
import type { TriggerDevice } from '@/views/rule-engine/Scene/typings'
const sceneStore = useSceneStore()

View File

@ -0,0 +1,63 @@
<template>
<j-modal
title="延迟执行"
visible
:width="400"
@cancel="onCancel"
@ok="onOk"
:maskClosable="false"
>
<a-input-number
style="max-width: 220px"
placeholder="请输入时间"
v-model:value="value"
:precision="3"
:min="0"
:max="6535"
>
<template #addonAfter>
<a-select
:options="[
{ label: '秒', value: 'seconds' },
{ label: '分', value: 'minutes' },
{ label: '小时', value: 'hours' },
]"
v-model:value="unit"
/>
</template>
</a-input-number>
</j-modal>
</template>
<script lang="ts" setup>
import { onlyMessage } from '@/utils/comm';
const timeUnitEnum = {
seconds: '秒',
minutes: '分',
hours: '小时',
};
const emit = defineEmits(['cancel', 'save']);
const value = ref<number>(0);
const unit = ref<'seconds' | 'minutes' | 'hours'>('seconds');
const onCancel = () => {
emit('cancel');
};
const onOk = () => {
if (unref(value) || unref(value) === 0) {
} else {
onlyMessage('请输入时间', 'error');
}
emit(
'save',
{
time: value.value,
unit: unit.value,
},
{ name: `${value.value} ${timeUnitEnum[unit.value]}后,执行后续动作` },
);
};
</script>

View File

@ -0,0 +1,22 @@
<template>
<j-modal
title="执行动作"
visible
:width="860"
@cancel="onCancel"
@ok="onOk"
:maskClosable="false"
>
device
</j-modal>
</template>
<script lang="ts" setup>
const emit = defineEmits(['cancel', 'save']);
const onCancel = () => {
emit('cancel');
};
const onOk = () => {
emit('save');
};
</script>

View File

@ -0,0 +1,3 @@
<template>
<div>ITEM</div>
</template>

View File

@ -0,0 +1,74 @@
<template>
<div class="action-list-content">
<div class="actions-add-list" :class="{'border': props.actions.length}">
<j-button type="primary" ghost style="width: 100%" @click="onAdd">
<template #icon><AIcon type="PlusOutlined" /></template>
添加执行动作
</j-button>
</div>
<Modal v-if="visible" @cancel="visible = false" />
</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue';
import { ActionsType, ParallelType } from '../../../typings';
import Modal from '../Modal/index.vue'
interface ListProps {
branchesName: number;
type: ParallelType;
actions: ActionsType[];
parallel: boolean;
}
const props = defineProps({
branchesName: Number,
type: {
type: String as PropType<ListProps['type']>,
default: 'serial',
},
actions: {
type: Array as PropType<ListProps['actions']>,
default: () => [],
},
parallel: Boolean,
});
const visible = ref<boolean>(false)
const onAdd = () => {
visible.value = true
}
</script>
<style lang="less" scoped>
.action-list-content {
padding: 8px;
.actions-list_form {
.action-list-add {
display: flex;
justify-content: flex-end;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid @primary-color;
}
}
.filter-add-button {
width: 100%;
color: rgba(0, 0, 0, 0.3);
text-align: center;
cursor: pointer;
}
.actions-add-list {
&.border {
padding-top: 16px;
border-top: 1px solid @primary-color;
}
}
}
</style>

View File

@ -0,0 +1,2 @@
export { default as List } from './List.vue';
export { default as Item } from './Item.vue';

View File

@ -0,0 +1,105 @@
<template>
<j-modal
title="类型"
visible
:width="860"
@cancel="onCancel"
@ok="onOk"
:maskClosable="false"
>
<a-form ref="actionForm" :model="formModel" layout="vertical">
<a-form-item
label="类型"
name="type"
:rules="[
{
required: true,
message: '请选择类型',
},
]"
>
<j-card-select
v-model:value="formModel.type"
:options="options"
type="horizontal"
float="right"
/>
</a-form-item>
<template v-if="actionType === 'device'">
<Device @cancel="onCancel" @save="onPropsOk" />
</template>
<template v-else-if="actionType === 'notify'">
<Notify @cancel="onCancel" @save="onPropsOk" />
</template>
<template v-else-if="actionType === 'delay'">
<Delay @cancel="onCancel" @save="onPropsOk" />
</template>
</a-form>
</j-modal>
</template>
<script lang="ts" setup>
import { getImage } from '@/utils/comm';
import Delay from '../Delay/index.vue'
import Notify from '../Notify/index.vue'
import Device from '../Device/index.vue'
const emit = defineEmits(['cancel', 'save']);
const options = [
{
label: '设备输出',
value: 'device',
iconUrl: getImage('/scene/device-type.png'),
subLabel: '配置设备调用功能、读取属性、设置属性规则',
},
{
label: '消息通知',
value: 'notify',
iconUrl: getImage('/scene/message-type.png'),
subLabel: '配置向指定用户发邮件、钉钉、微信、短信等通知',
},
{
label: '延迟执行',
value: 'delay',
iconUrl: getImage('/scene/delay-type.png'),
subLabel: '等待一段时间后,再执行后续动作',
},
{
label: '触发告警',
value: 'trigger',
iconUrl: getImage('/scene/trigger-type.png'),
subLabel: '配置触发告警规则,需配合“告警配置”使用',
},
{
label: '解除告警',
value: 'relieve',
iconUrl: getImage('/scene/cancel-type.png'),
subLabel: '配置解除告警规则,需配合“告警配置”使用',
},
];
const actionForm = ref();
const formModel = reactive({
type: '',
});
const actionType = ref<string>('')
const onCancel = () => {
emit('cancel');
};
const onOk = () => {
actionForm.value.validate().then((values: any) => {
actionType.value = values?.type
if (values?.type === 'relieve' || values?.type === 'trigger') {
// emit('save');
// props.save({ ...props.data, executor: 'alarm', alarm: { mode: values.type } }, {});
}
});
};
const onPropsOk = (data: any, option: any) => {
console.log(data, option)
}
</script>

View File

@ -0,0 +1,178 @@
<template>
<Search
:columns="columns"
type="simple"
target="action-notice-config"
@search="handleSearch"
class="search"
/>
<div style="height: 400px; overflow-y: auto">
<JTable
:columns="columns"
:request="ConfigApi.list"
model="CARD"
:bodyStyle="{
paddingRight: 0,
paddingLeft: 0,
}"
:defaultParams="{
terms: [
{
terms: [
{
termType: 'eq',
column: 'type',
value: props.notifyType,
},
],
},
],
sorts: [{ name: 'createTime', order: 'desc' }],
}"
:params="params"
:gridColumn="2"
:gridColumns="[2, 2, 2]"
:rowSelection="{
selectedRowKeys: _selectedRowKeys,
}"
@cancelSelect="cancelSelect"
>
<template #card="slotProps">
<CardBox
:showStatus="false"
:value="slotProps"
:showTool="false"
:actions="[]"
v-bind="slotProps"
@click="handleClick"
:active="_selectedRowKeys.includes(slotProps.id)"
>
<template #img>
<slot name="img">
<img
:src="
getLogo(slotProps.type, slotProps.provider)
"
class="logo"
/>
</slot>
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)">
<span style="font-size: 16px; font-weight: 600">
{{ slotProps.name }}
</span>
</Ellipsis>
<a-row>
<a-col :span="12">
<div class="card-item-content-text">
通知方式
</div>
<div>
{{ getMethodTxt(slotProps.type) }}
</div>
</a-col>
<a-col :span="12">
<div class="card-item-content-text">说明</div>
<Ellipsis>
{{ slotProps.description }}
</Ellipsis>
</a-col>
</a-row>
</template>
</CardBox>
</template>
</JTable>
</div>
</template>
<script lang="ts" setup>
import ConfigApi from '@/api/notice/config';
import { MSG_TYPE, NOTICE_METHOD } from '@/views/notice/const';
const props = defineProps({
notifyType: {
type: String,
default: '',
},
value: {
type: String,
default: '',
},
});
const emit = defineEmits(['update:value']);
const getLogo = (type: string, provider: string) => {
return MSG_TYPE[type].find((f: any) => f.value === provider)?.logo;
};
const getMethodTxt = (type: string) => {
return NOTICE_METHOD.find((f) => f.value === type)?.label;
};
const params = ref<Record<string, any>>({});
const _selectedRowKeys = ref<string[]>([]);
const columns = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
search: {
type: 'string',
},
},
{
title: 'ID',
dataIndex: 'id',
key: 'id',
search: {
type: 'string',
},
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
search: {
type: 'string',
},
},
];
const handleSearch = (_params: any) => {
params.value = _params;
};
const cancelSelect = () => {
_selectedRowKeys.value = [];
};
const handleClick = (dt: any) => {
_selectedRowKeys.value = [dt.id];
emit('update:value', dt.id);
};
watch(
() => props.value,
(newValue) => {
if (newValue) {
_selectedRowKeys.value = [newValue];
} else {
_selectedRowKeys.value = [];
}
},
{
deep: true,
immediate: true,
},
);
</script>
<style lang="less" scoped>
.search {
margin-bottom: 0;
padding-right: 0px;
padding-left: 0px;
}
</style>

View File

@ -0,0 +1,167 @@
<template>
<Search
:columns="columns"
type="simple"
target="action-notice-template"
@search="handleSearch"
class="search"
/>
<div style="height: 400px; overflow-y: auto">
<JTable
:columns="columns"
:request="(e) => TemplateApi.getListByConfigId(props.notifierId, e)"
model="CARD"
:bodyStyle="{
paddingRight: 0,
paddingLeft: 0,
}"
:defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }],
}"
:params="params"
:gridColumn="2"
:gridColumns="[2, 2, 2]"
:rowSelection="{
selectedRowKeys: _selectedRowKeys,
}"
@cancelSelect="cancelSelect"
>
<template #card="slotProps">
<CardBox
:showStatus="false"
:value="slotProps"
:showTool="false"
:actions="[]"
v-bind="slotProps"
@click="handleClick"
:active="_selectedRowKeys.includes(slotProps.id)"
>
<template #img>
<slot name="img">
<img
:src="
getLogo(slotProps.type, slotProps.provider)
"
class="logo"
/>
</slot>
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)">
<span style="font-size: 16px; font-weight: 600">
{{ slotProps.name }}
</span>
</Ellipsis>
<a-row>
<a-col :span="12">
<div class="card-item-content-text">
通知方式
</div>
<div>
{{ getMethodTxt(slotProps.type) }}
</div>
</a-col>
<a-col :span="12">
<div class="card-item-content-text">说明</div>
<Ellipsis>
{{ slotProps.description }}
</Ellipsis>
</a-col>
</a-row>
</template>
</CardBox>
</template>
</JTable>
</div>
</template>
<script lang="ts" setup>
import TemplateApi from '@/api/notice/template';
import { MSG_TYPE, NOTICE_METHOD } from '@/views/notice/const';
const props = defineProps({
notifierId: {
type: String,
default: '',
},
value: {
type: String,
default: '',
},
});
const emit = defineEmits(['update:value']);
const getLogo = (type: string, provider: string) => {
return MSG_TYPE[type].find((f: any) => f.value === provider)?.logo;
};
const getMethodTxt = (type: string) => {
return NOTICE_METHOD.find((f) => f.value === type)?.label;
};
const params = ref<Record<string, any>>({});
const _selectedRowKeys = ref<string[]>([]);
const columns = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
search: {
type: 'string',
},
},
{
title: 'ID',
dataIndex: 'id',
key: 'id',
search: {
type: 'string',
},
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
search: {
type: 'string',
},
},
];
const handleSearch = (_params: any) => {
params.value = _params;
};
const cancelSelect = () => {
_selectedRowKeys.value = [];
};
const handleClick = (dt: any) => {
_selectedRowKeys.value = [dt.id];
emit('update:value', dt.id);
};
watch(
() => props.value,
(newValue) => {
if (newValue) {
_selectedRowKeys.value = [newValue];
} else {
_selectedRowKeys.value = [];
}
},
{
deep: true,
immediate: true,
},
);
</script>
<style lang="less" scoped>
.search {
margin-bottom: 0;
padding-right: 0px;
padding-left: 0px;
}
</style>

View File

@ -0,0 +1,61 @@
<template>
<a-spin :spinning="loading">
<j-card-select
v-model:value="notifyType"
:options="options"
:icon-size="106"
/>
</a-spin>
</template>
<script lang="ts" setup>
import { getImage } from '@/utils/comm';
import notice from '@/api/notice/config';
const iconMap = new Map();
iconMap.set('dingTalk', getImage('/notice/dingtalk.png'));
iconMap.set('weixin', getImage('/notice/wechat.png'));
iconMap.set('email', getImage('/notice/email.png'));
iconMap.set('voice', getImage('/notice/voice.png'));
iconMap.set('sms', getImage('/notice/sms.png'));
iconMap.set('webhook', getImage('/notice/webhook.png'));
const props = defineProps({
value: {
type: String,
default: '',
},
});
const emit = defineEmits(['update:value'])
const loading = ref<boolean>(false);
const notifyType = ref('');
const options = ref<any[]>([]);
watch(
() => notifyType,
(newVal) => {
emit('update:value', newVal)
},
{ deep: true, immediate: true },
);
onMounted(() => {
notice.queryMessageType().then((resp) => {
if (resp.status === 200) {
options.value = (resp.result as any[]).map((item) => {
return {
label: item.name,
value: item.id,
iconUrl: iconMap.get(item.id),
};
});
}
});
notifyType.value = props.value
});
</script>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,70 @@
<template>
<a-form
v-if="variableDefinitions.length"
:layout="'vertical'"
ref="formRef"
:model="modelRef"
>
<template v-for="item in variableDefinitions" :key="item.id">
<a-form-item
:name="item.id"
:label="item.name"
:rules="[{ required: true, message: `请输入${item.name}` }]"
>
<User
v-if="getType(item) === 'user'"
v-model="modelRef[`${item.id}`]"
/>
<Org
v-else-if="getType(item) === 'org'"
v-model="modelRef[`${item.id}`]"
/>
<Tag
v-else-if="getType(item) === 'tag'"
v-model="modelRef[`${item.id}`]"
/>
<InputFile
v-else-if="getType(item) === 'file'"
v-model="modelRef[`${item.id}`]"
/>
<a-input
v-else-if="getType(item) === 'link'"
v-model="modelRef[`${item.id}`]"
/>
<BuildIn
v-else
v-model="modelRef[`${item.id}`]"
/>
</a-form-item>
</template>
</a-form>
</template>
<script lang="ts" setup>
import BuildIn from './variableItem/BuildIn.vue';
import Org from './variableItem/Org.vue';
import Tag from './variableItem/Tag.vue';
import InputFile from './variableItem/InputFile.vue';
import User from './variableItem/User.vue';
const props = defineProps({
variableDefinitions: {
type: Array,
default: () => [],
},
value: {
type: Object,
default: () => {},
},
});
const formRef = ref();
const modelRef = reactive({});
Object.assign(formRef, props.value);
const getType = (item: any) => {
return item.expands?.businessType || item.type;
};
</script>

View File

@ -0,0 +1,146 @@
<template>
<j-modal
title="执行动作"
visible
:width="860"
@cancel="onCancel"
@ok="onOk"
:maskClosable="false"
>
<div class="steps-steps">
<a-steps :current="current" size="small" @change="onChange">
<a-step title="通知方式" key="way" />
<a-step title="通知配置" key="config" />
<a-step title="通知模板" key="template" />
<a-step title="模板变量" key="variable" />
</a-steps>
</div>
<div class="steps-content">
<a-form ref="actionForm" :model="formModel" layout="vertical">
<template v-if="current === 0">
<a-form-item
label="应用"
name="notifyType"
:rules="[
{
required: true,
message: '请选择通知方式',
},
]"
>
<NotifyWay v-model:value="formModel.notifyType" />
</a-form-item>
</template>
<template v-if="current === 1">
<a-form-item name="notifierId">
<NotifyConfig v-model:value="formModel.notifierId" :notifyType="formModel.notifyType" />
</a-form-item>
</template>
<template v-if="current === 2">
<a-form-item name="templateId">
<NotifyTemplate v-model:value="formModel.templateId" :notifierId="formModel.notifierId" />
</a-form-item>
</template>
<template v-if="current === 3">
<a-form-item name="variables">
<VariableDefinitions
:variableDefinitions="variable"
v-model:value="formModel.variables"
/>
</a-form-item>
</template>
</a-form>
</div>
<template #footer>
<a-space>
<j-button v-if="current === 0" @click="onCancel">取消</j-button>
<j-button v-if="current > 0" @click="prev">上一步</j-button>
<j-button v-if="current < 3" type="primary" @click="next">下一步</j-button>
<j-button v-if="current === 3" type="primary" @click="onOk">确定</j-button>
</a-space>
</template>
</j-modal>
</template>
<script lang="ts" setup>
import NotifyWay from './NotifyWay.vue';
import NotifyConfig from './NotifyConfig.vue';
import NotifyTemplate from './NotifyTemplate.vue';
import VariableDefinitions from './VariableDefinitions.vue';
import { onlyMessage } from '@/utils/comm';
import Template from '@/api/notice/template';
const emit = defineEmits(['cancel', 'save']);
const current = ref(0);
const formModel = reactive({
notifyType: '',
notifierId: '',
templateId: '',
variables: [],
});
const variable = ref([]);
const jumpStep = async (val: number) => {
if (val === 0) {
current.value = val;
} else if (val === 1) {
if (formModel.notifyType) {
current.value = val
} else {
onlyMessage('请选择通知方式', 'error');
}
} else if (val === 2) {
if (formModel.notifierId) {
current.value = val
} else {
onlyMessage('请选择通知配置', 'error');
}
} else if (val === 3) {
formModel.templateId = '1628943618904956928'
if (formModel.templateId) {
const resp = await Template.getTemplateDetail(formModel.templateId);
if (resp.status === 200) {
variable.value = resp.result?.variableDefinitions || [];
current.value = val
}
} else {
onlyMessage('请选择通知模板', 'error');
}
}
};
const onChange = (cur: number) => {
jumpStep(cur)
};
const prev = () => {
current.value -= 1;
};
const next = async () => {
jumpStep(current.value + 1)
};
const onCancel = () => {
emit('cancel');
};
const onOk = () => {
emit('save');
};
</script>
<style lang="less" scoped>
.steps-steps {
width: 100%;
margin-bottom: 17px;
padding-bottom: 17px;
border-bottom: 1px solid #f0f0f0;
}
.steps-content {
width: 100%;
}
</style>

View File

@ -0,0 +1,3 @@
<template>
build-in
</template>

View File

@ -0,0 +1,3 @@
<template>
input-file
</template>

View File

@ -0,0 +1,3 @@
<template>
org
</template>

View File

@ -0,0 +1,3 @@
<template>
tag
</template>

View File

@ -0,0 +1,3 @@
<template>
user
</template>

View File

@ -0,0 +1,133 @@
<template>
<div class="actions">
<div class="actions-title">
<span>执行</span>
<ShakeLimit
v-if="props.openShakeLimit"
v-model:value="shakeLimit"
/>
</div>
<div class="actions-warp">
<a-collapse v-model:activeKey="activeKeys">
<a-collapse-panel key="1">
<template #header>
<span>
串行
<span class="panel-tip">
按顺序依次执行动作适用于基于动作输出参数判断是否执行后续动作的场景
</span>
</span>
</template>
<div class="actions-list">
<List
type="serial"
:branchesName="props.name"
:parallel="false"
:actions="serialArray.length ? serialArray[0].actions : []"
/>
</div>
</a-collapse-panel>
<a-collapse-panel key="2">
<template #header>
<span>
并行
<span class="panel-tip">
同时执行所有动作适用于不需要关注执行动作先后顺序和结果的场景
</span>
</span>
</template>
<div class="actions-list">
<List
type="parallel"
:branchesName="props.name"
:parallel="true"
:actions="parallelArray.length ? parallelArray[0].actions : []"
/>
</div>
</a-collapse-panel>
</a-collapse>
</div>
</div>
</template>
<script lang="ts" setup>
import ShakeLimit from '../components/ShakeLimit/index.vue';
import { List } from './ListItem';
import { storeToRefs } from 'pinia';
import { useSceneStore } from '@/store/scene';
import { BranchesThen } from '../../typings';
import { PropType } from 'vue';
interface ActionsProps {
name: number;
openShakeLimit?: boolean;
thenOptions: BranchesThen[];
}
const props = defineProps({
name: Number,
thenOptions: {
type: Array as PropType<ActionsProps['thenOptions']>,
default: () => [],
},
openShakeLimit: Boolean,
});
const shakeLimit = ref({});
const activeKeys = ref<string[]>(['1']);
const parallelArray = ref<BranchesThen[]>([]);
const serialArray = ref<BranchesThen[]>([]);
const lock = ref<boolean>(false)
watch(
() => props.thenOptions,
(newVal) => {
parallelArray.value = newVal.filter((item) => item.parallel);
serialArray.value = newVal.filter((item) => !item.parallel);
const isSerialActions = serialArray.value.some((item) => {
return !!item.actions.length;
});
if (
!lock.value &&
parallelArray.value.length &&
(!serialArray.value.length || !isSerialActions)
) {
activeKeys.value = ['2']
lock.value = true
}
//TODO
},
{
deep: true,
immediate: true,
},
);
</script>
<style lang="less" scoped>
.actions {
.actions-title {
display: flex;
gap: 16px;
align-items: center;
margin-bottom: 16px;
font-weight: 800;
font-size: 14px;
line-height: 32px;
}
.actions-warp {
.actions-list {
width: 100%;
}
}
.panel-tip {
padding-left: 8px;
color: rgba(#000, 0.45);
}
}
</style>

View File

@ -0,0 +1,85 @@
<template>
<div class="shakeLimit">
<a-switch
checkedChildren="开启防抖"
unCheckedChildren="关闭防抖"
v-model:checked="shakeLimit.enabled"
style="margin-right: 12px"
/>
<template v-if="shakeLimit.enabled">
<a-input-number :min="1" :max="100" :precision="0" size="small" v-model:value="shakeLimit.time" style="width: 32px" />
<span>秒内发送</span>
<a-input-number :min="1" :max="100" :precision="0" size="small" v-model:value="shakeLimit.threshold" style="width: 32px" />
<span>次及以上时处理</span>
<a-radio-group :options="alarmFirstOptions" optionType="button" v-model:value="shakeLimit.alarmFirst" size="small" />
</template>
</div>
</template>
<script lang="ts" setup>
import { cloneDeep } from "lodash-es";
import { PropType } from "vue";
type ShakeLimitType = {
enabled: boolean | undefined,
time?: number | undefined | null,
threshold?: number | undefined | null,
alarmFirst?: boolean | undefined
}
type Emit = {
(e: 'update:value', data: ShakeLimitType): void
}
const alarmFirstOptions = [
{ label: '第一次', value: true },
{ label: '最后一次', value: false },
];
const props = defineProps({
value: {
type: Object as PropType<ShakeLimitType>,
default: () => ({})
}
})
const emit = defineEmits<Emit>()
const shakeLimit = reactive<ShakeLimitType>({
enabled: undefined,
time: 1,
threshold: 1,
alarmFirst: undefined
})
Object.assign(shakeLimit, props.value)
watch(() => shakeLimit, () => {
const cloneValue = cloneDeep(shakeLimit)
emit('update:value', cloneValue)
}, {
deep: true
})
</script>
<style lang="less" scoped>
.shakeLimit {
display: flex;
gap: 4px;
align-items: center;
font-weight: 400;
font-size: 14px;
:deep(.ant-input-number-handler-wrap) {
display: none;
}
:deep(.ant-radio-button-wrapper) {
padding: 0 16px;
}
input {
padding: 0 4px;
}
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<page-container>
<Search :columns="columns" target="scene" @search="handleSearch" />
<JTable
<JProTable
ref="sceneRef"
:columns="columns"
:request="query"
@ -9,7 +9,7 @@
:params="params"
>
<template #headerTitle>
<a-space>
<j-space>
<PermissionButton
type="primary"
@click="handleAdd"
@ -18,7 +18,7 @@
<template #icon><AIcon type="PlusOutlined" /></template>
新增
</PermissionButton>
</a-space>
</j-space>
</template>
<template #card="slotProps">
<SceneCard
@ -90,13 +90,13 @@
{{ typeMap.get(slotProps.triggerType)?.text }}
</template>
<template #state="slotProps">
<a-badge
<j-badge
:text="slotProps.state?.text"
:status="statusMap.get(slotProps.state?.value)"
/>
</template>
<template #action="slotProps">
<a-space>
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
@ -115,9 +115,9 @@
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</a-space>
</j-space>
</template>
</JTable>
</JProTable>
<SaveModal v-if="visible" @close="visible = false" :data="current" />
</page-container>
</template>

View File

@ -92,7 +92,7 @@
:type="
slotProps.status
? 'StopOutlined'
: 'PlayCircleOutlined '
: 'PlayCircleOutlined'
"
/>
</PermissionButton>