Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev
This commit is contained in:
commit
82fccd6be5
|
@ -59,7 +59,16 @@ export const category = (data: any) => server.post('/device/category/_tree', dat
|
|||
* 获取接入方式
|
||||
* @param data 查询条件
|
||||
*/
|
||||
export const queryGatewayList = (data: any) => server.post('/gateway/device/_query/no-paging', data)
|
||||
const defaultGatewayData = {
|
||||
paging: false,
|
||||
sorts: [
|
||||
{
|
||||
name: 'createTime',
|
||||
order: 'desc',
|
||||
},
|
||||
],
|
||||
}
|
||||
export const queryGatewayList = (data: any = defaultGatewayData) => server.post('/gateway/device/_query/no-paging', data)
|
||||
|
||||
/**
|
||||
* 查询产品列表(分页)
|
||||
|
|
|
@ -11,8 +11,8 @@ export default {
|
|||
// 修改
|
||||
update: (data: any) => patch(`/notifier/template`, data),
|
||||
del: (id: any) => remove(`/notifier/template/${id}`),
|
||||
getConfig: (data: any) => post<BindConfig>(`/notifier/config/_query/no-paging?paging=false`, data),
|
||||
getTemplateDetail: (id: string) => get(`/notifier/template/${id}/detail`),
|
||||
getConfig: (data: any) => post<BindConfig[]>(`/notifier/config/_query/no-paging?paging=false`, data),
|
||||
getTemplateDetail: (id: string) => get<any>(`/notifier/template/${id}/detail`),
|
||||
debug: (data: any, configId: string, templateId: string) => post(`/notifier/${configId}/${templateId}/_send`, data),
|
||||
getHistory: (data: any, id: string) => post(`/notify/history/template/${id}/_query`, data),
|
||||
// 钉钉/微信, 根据配置获取部门和用户
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import server from '@/utils/request';
|
||||
/**
|
||||
* 获取告警配置列表
|
||||
*/
|
||||
export const queryList = (data:any) => server.post('/alarm/config/detail/_query',data);
|
||||
/**
|
||||
* 启动告警配置
|
||||
*/
|
||||
export const _enable = (id:string) => server.post(`/alarm/config/${id}/_enable`);
|
||||
/**
|
||||
* 禁用告警配置
|
||||
*/
|
||||
export const _disable = (id:string) => server.post(`/alarm/config/${id}/_disable`);
|
||||
/**
|
||||
* 删除告警配置
|
||||
*/
|
||||
export const remove = (id:string) => server.remove(`/alarm/config/${id}`);
|
||||
/**
|
||||
* 手动触发告警
|
||||
*/
|
||||
export const _execute = (data:any) => server.post('/scene/batch/_execute',data)
|
|
@ -51,7 +51,9 @@ const iconKeys = [
|
|||
'playCircleOutlined',
|
||||
'RightOutlined',
|
||||
'FileTextOutlined',
|
||||
'UploadOutlined'
|
||||
'UploadOutlined',
|
||||
'LikeOutlined',
|
||||
'ArrowLeftOutlined'
|
||||
]
|
||||
|
||||
const Icon = (props: {type: string}) => {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<a-popconfirm v-bind="popConfirm" :disabled="!isPermission || props.disabled">
|
||||
<a-tooltip v-if="tooltip" v-bind="tooltip">
|
||||
<slot v-if="noButton"></slot>
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" >
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" :style="props.style">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
|
@ -22,7 +22,7 @@
|
|||
<template v-else-if="tooltip">
|
||||
<a-tooltip v-bind="tooltip">
|
||||
<slot v-if="noButton"></slot>
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" >
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" :style="props.style">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
|
@ -32,7 +32,7 @@
|
|||
</template>
|
||||
<template v-else>
|
||||
<slot v-if="noButton"></slot>
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" >
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" :style="props.style">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
|
@ -42,7 +42,7 @@
|
|||
</template>
|
||||
<a-tooltip v-else title="没有权限">
|
||||
<slot v-if="noButton"></slot>
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" >
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" :style="props.style">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
|
@ -51,7 +51,7 @@
|
|||
</a-tooltip>
|
||||
</template>
|
||||
<script setup lang="ts" name="PermissionButton">
|
||||
import { PropType } from 'vue'
|
||||
import { CSSProperties, PropType } from 'vue'
|
||||
import { TooltipProps, PopconfirmProps } from 'ant-design-vue/es'
|
||||
import { buttonProps } from 'ant-design-vue/es/button/button'
|
||||
import { usePermissionStore } from '@/store/permission';
|
||||
|
@ -85,6 +85,9 @@ const props = defineProps({
|
|||
hasPermission: {
|
||||
type: String || Array,
|
||||
},
|
||||
style: {
|
||||
type: Object as PropType<CSSProperties>
|
||||
},
|
||||
...buttonProps()
|
||||
})
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class='JSearch-warp' ref='searchRef'>
|
||||
<div :class="['JSearch-warp', props.class]" ref='searchRef'>
|
||||
<!-- 高级模式 -->
|
||||
<div v-if='props.type === "advanced"' :class='["JSearch-content senior", expand ? "senior-expand" : "", screenSize ? "big" : "small"]'>
|
||||
<div :class='["JSearch-items", expand ? "items-expand" : "", layout]'>
|
||||
|
@ -94,6 +94,10 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: '',
|
||||
required: true
|
||||
},
|
||||
class: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -235,7 +239,7 @@ const reset = () => {
|
|||
urlParams.target = null
|
||||
}
|
||||
resetNumber.value += 1
|
||||
emit('search', terms)
|
||||
emit('search', { terms: []})
|
||||
}
|
||||
|
||||
watch(width, (value) => {
|
||||
|
@ -375,10 +379,10 @@ handleItems()
|
|||
|
||||
&.simple {
|
||||
.JSearch-items {
|
||||
flex-grow: 4;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.JSearch-footer {
|
||||
flex-grow: 3;
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,6 +154,7 @@ const JTable = defineComponent<JTableProps>({
|
|||
const pageSize = ref<number>(6)
|
||||
const total = ref<number>(0)
|
||||
const loading = ref<boolean>(true)
|
||||
const loading1 = ref<boolean>(true)
|
||||
|
||||
const _columns = computed(() => props.columns.filter(i => !(i?.hideInTable)))
|
||||
|
||||
|
@ -244,10 +245,6 @@ const JTable = defineComponent<JTableProps>({
|
|||
window.onresize = null
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
// console.log(props.bodyStyle)
|
||||
})
|
||||
|
||||
/**
|
||||
* 刷新数据
|
||||
* @param _params
|
||||
|
@ -292,92 +289,94 @@ const JTable = defineComponent<JTableProps>({
|
|||
</div>
|
||||
</div>
|
||||
{/* content */}
|
||||
<div class={styles['jtable-content']}>
|
||||
{
|
||||
props.alertRender && props?.rowSelection && props?.rowSelection?.selectedRowKeys && props.rowSelection.selectedRowKeys?.length ?
|
||||
<div class={styles['jtable-alert']}>
|
||||
<Alert
|
||||
message={'已选择' + props?.rowSelection?.selectedRowKeys?.length + '项'}
|
||||
type="info"
|
||||
onClose={() => {
|
||||
emit('cancelSelect')
|
||||
}}
|
||||
closeText={<a-button type="link">取消选择</a-button>}
|
||||
/>
|
||||
</div> : null
|
||||
}
|
||||
{
|
||||
_model.value === ModelEnum.CARD ?
|
||||
<div class={styles['jtable-card']}>
|
||||
{
|
||||
_dataSource.value.length ?
|
||||
<div
|
||||
class={styles['jtable-card-items']}
|
||||
style={{ gridTemplateColumns: `repeat(${column.value}, 1fr)` }}
|
||||
>
|
||||
{
|
||||
_dataSource.value.map(item => slots.card ?
|
||||
<div class={[styles['jtable-card-item'], props.cardBodyClass]}>
|
||||
{slots.card(item)}
|
||||
</div> : null
|
||||
)
|
||||
}
|
||||
</div> :
|
||||
<div><JEmpty style="margin: 10% 0" /></div>
|
||||
}
|
||||
</div> :
|
||||
<div>
|
||||
<Table
|
||||
dataSource={_dataSource.value}
|
||||
columns={_columns.value}
|
||||
pagination={false}
|
||||
rowKey="id"
|
||||
rowSelection={props.rowSelection}
|
||||
scroll={{ x: 1366 }}
|
||||
v-slots={{
|
||||
bodyCell: (dt: Record<string, any>) => {
|
||||
const { column, record } = dt;
|
||||
if ((column?.key || column?.dataIndex) && column?.scopedSlots && (slots?.[column?.dataIndex] || slots?.[column?.key])) {
|
||||
const _key = column?.key || column?.dataIndex
|
||||
return slots?.[_key]!(record)
|
||||
} else {
|
||||
return record?.[column?.dataIndex] || ''
|
||||
}
|
||||
},
|
||||
emptyText: () => <JEmpty style="margin: 10% 0" />
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{
|
||||
!loading.value ? <div class={styles['jtable-content']}>
|
||||
{
|
||||
props.alertRender && props?.rowSelection && props?.rowSelection?.selectedRowKeys && props.rowSelection.selectedRowKeys?.length ?
|
||||
<div class={styles['jtable-alert']}>
|
||||
<Alert
|
||||
message={'已选择' + props?.rowSelection?.selectedRowKeys?.length + '项'}
|
||||
type="info"
|
||||
onClose={() => {
|
||||
emit('cancelSelect')
|
||||
}}
|
||||
closeText={<a-button type="link">取消选择</a-button>}
|
||||
/>
|
||||
</div> : null
|
||||
}
|
||||
{
|
||||
_model.value === ModelEnum.CARD ?
|
||||
<div class={styles['jtable-card']}>
|
||||
{
|
||||
_dataSource.value.length ?
|
||||
<div
|
||||
class={styles['jtable-card-items']}
|
||||
style={{ gridTemplateColumns: `repeat(${column.value}, 1fr)` }}
|
||||
>
|
||||
{
|
||||
_dataSource.value.map(item => slots.card ?
|
||||
<div class={[styles['jtable-card-item'], props.cardBodyClass]}>
|
||||
{slots.card(item)}
|
||||
</div> : null
|
||||
)
|
||||
}
|
||||
</div> :
|
||||
<div><JEmpty style="margin: 10% 0" /></div>
|
||||
}
|
||||
</div> :
|
||||
<div>
|
||||
<Table
|
||||
dataSource={_dataSource.value}
|
||||
columns={_columns.value}
|
||||
pagination={false}
|
||||
rowKey="id"
|
||||
rowSelection={props.rowSelection}
|
||||
scroll={{ x: 1366 }}
|
||||
v-slots={{
|
||||
bodyCell: (dt: Record<string, any>) => {
|
||||
const { column, record } = dt;
|
||||
if ((column?.key || column?.dataIndex) && column?.scopedSlots && (slots?.[column?.dataIndex] || slots?.[column?.key])) {
|
||||
const _key = column?.key || column?.dataIndex
|
||||
return slots?.[_key]!(record)
|
||||
} else {
|
||||
return record?.[column?.dataIndex] || ''
|
||||
}
|
||||
},
|
||||
emptyText: () => <JEmpty style="margin: 10% 0" />
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div> : <div style="width: 100%; height: 400px"></div>
|
||||
}
|
||||
{/* 分页 */}
|
||||
{
|
||||
(!!_dataSource.value.length) && !props.noPagination && props.type === 'PAGE' &&
|
||||
<div class={styles['jtable-pagination']}>
|
||||
{
|
||||
slots?.paginationRender ?
|
||||
slots.paginationRender() :
|
||||
<Pagination
|
||||
size="small"
|
||||
total={total.value}
|
||||
showQuickJumper={false}
|
||||
showSizeChanger={true}
|
||||
current={pageIndex.value + 1}
|
||||
pageSize={pageSize.value}
|
||||
pageSizeOptions={['12', '24', '48', '60', '100']}
|
||||
showTotal={(num) => {
|
||||
const minSize = pageIndex.value * pageSize.value + 1;
|
||||
const MaxSize = (pageIndex.value + 1) * pageSize.value;
|
||||
return `第 ${minSize} - ${MaxSize > num ? num : MaxSize} 条/总共 ${num} 条`;
|
||||
}}
|
||||
onChange={(page, size) => {
|
||||
handleSearch({
|
||||
...props.params,
|
||||
pageSize: size,
|
||||
pageIndex: pageSize.value === size ? (page ? page - 1 : 0) : 0
|
||||
})
|
||||
}}
|
||||
/>
|
||||
slots?.paginationRender ?
|
||||
slots.paginationRender() :
|
||||
<Pagination
|
||||
size="small"
|
||||
total={total.value}
|
||||
showQuickJumper={false}
|
||||
showSizeChanger={true}
|
||||
current={pageIndex.value + 1}
|
||||
pageSize={pageSize.value}
|
||||
pageSizeOptions={['12', '24', '48', '60', '100']}
|
||||
showTotal={(num) => {
|
||||
const minSize = pageIndex.value * pageSize.value + 1;
|
||||
const MaxSize = (pageIndex.value + 1) * pageSize.value;
|
||||
return `第 ${minSize} - ${MaxSize > num ? num : MaxSize} 条/总共 ${num} 条`;
|
||||
}}
|
||||
onChange={(page, size) => {
|
||||
handleSearch({
|
||||
...props.params,
|
||||
pageSize: size,
|
||||
pageIndex: pageSize.value === size ? (page ? page - 1 : 0) : 0
|
||||
})
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
<template>
|
||||
<div class="title">
|
||||
<div class="title" :style='style'>
|
||||
<div class="title-before"></div>
|
||||
<span>{{ data }}</span>
|
||||
<slot name="extra"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "TitleComponent",
|
||||
props: {
|
||||
data: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
<script setup lang='ts' name='TitleComponent'>
|
||||
import type { CSSProperties, PropType } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
};
|
||||
style: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -67,66 +67,116 @@ const defaultOptions = {
|
|||
],
|
||||
};
|
||||
|
||||
export const useSceneStore = defineStore({
|
||||
id: 'scene',
|
||||
state: (): DataType => {
|
||||
return {
|
||||
data: {
|
||||
trigger: { type: ''},
|
||||
options: defaultOptions,
|
||||
branches: defaultBranches,
|
||||
description: ''
|
||||
},
|
||||
productCache: {}
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
initData() {
|
||||
export const useSceneStore = defineStore('scene', () => {
|
||||
const data = reactive<FormModelType | any>({
|
||||
trigger: { type: ''},
|
||||
options: defaultOptions,
|
||||
branches: defaultBranches,
|
||||
description: '',
|
||||
name: '',
|
||||
id: undefined
|
||||
})
|
||||
const productCache = {}
|
||||
|
||||
},
|
||||
/**
|
||||
* 获取详情
|
||||
* @param id
|
||||
*/
|
||||
async getDetail(id: string) {
|
||||
const resp = await detail(id)
|
||||
if (resp.success) {
|
||||
const result = resp.result as SceneItem
|
||||
const triggerType = result.triggerType
|
||||
let branches: any[] = result.branches
|
||||
const getDetail = async (id: string) => {
|
||||
const resp = await detail(id)
|
||||
if (resp.success) {
|
||||
const result = resp.result as SceneItem
|
||||
const triggerType = result.triggerType
|
||||
let branches: any[] = result.branches
|
||||
|
||||
if (!branches) {
|
||||
branches = cloneDeep(defaultBranches)
|
||||
if (triggerType === 'device') {
|
||||
branches.push(null)
|
||||
}
|
||||
} else {
|
||||
const branchesLength = branches.length;
|
||||
if (
|
||||
triggerType === 'device' &&
|
||||
((branchesLength === 1 && !!branches[0]?.when?.length) || // 有一组数据并且when有值
|
||||
(branchesLength > 1 && !branches[branchesLength - 1]?.when?.length)) // 有多组否则数据,并且最后一组when有值
|
||||
) {
|
||||
branches.push(null);
|
||||
}
|
||||
if (!branches) {
|
||||
branches = cloneDeep(defaultBranches)
|
||||
if (triggerType === 'device') {
|
||||
branches.push(null)
|
||||
}
|
||||
|
||||
this.data = {
|
||||
...result,
|
||||
trigger: result.trigger || {},
|
||||
branches: cloneDeep(assignmentKey(branches)),
|
||||
options: {...defaultOptions, ...result.options },
|
||||
} else {
|
||||
const branchesLength = branches.length;
|
||||
if (
|
||||
triggerType === 'device' &&
|
||||
((branchesLength === 1 && !!branches[0]?.when?.length) || // 有一组数据并且when有值
|
||||
(branchesLength > 1 && !branches[branchesLength - 1]?.when?.length)) // 有多组否则数据,并且最后一组when有值
|
||||
) {
|
||||
branches.push(null);
|
||||
}
|
||||
}
|
||||
},
|
||||
getProduct() {
|
||||
|
||||
Object.assign(data, {
|
||||
...result,
|
||||
trigger: result.trigger || {},
|
||||
branches: cloneDeep(assignmentKey(branches)),
|
||||
options: result.options ? {...defaultOptions, ...result.options } : defaultOptions,
|
||||
})
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
data,
|
||||
productCache,
|
||||
getDetail
|
||||
}
|
||||
})
|
||||
//
|
||||
// export const useSceneStore = defineStore({
|
||||
// id: 'scene',
|
||||
// state: (): DataType => {
|
||||
// return {
|
||||
// data: {
|
||||
// trigger: { type: ''},
|
||||
// options: defaultOptions,
|
||||
// branches: defaultBranches,
|
||||
// description: ''
|
||||
// },
|
||||
// productCache: {}
|
||||
// }
|
||||
// },
|
||||
// actions: {
|
||||
// /**
|
||||
// * 初始化数据
|
||||
// */
|
||||
// initData() {
|
||||
//
|
||||
// },
|
||||
// /**
|
||||
// * 获取详情
|
||||
// * @param id
|
||||
// */
|
||||
// async getDetail(id: string) {
|
||||
// const resp = await detail(id)
|
||||
// if (resp.success) {
|
||||
// const result = resp.result as SceneItem
|
||||
// const triggerType = result.triggerType
|
||||
// let branches: any[] = result.branches
|
||||
//
|
||||
// if (!branches) {
|
||||
// branches = cloneDeep(defaultBranches)
|
||||
// if (triggerType === 'device') {
|
||||
// branches.push(null)
|
||||
// }
|
||||
// } else {
|
||||
// const branchesLength = branches.length;
|
||||
// if (
|
||||
// triggerType === 'device' &&
|
||||
// ((branchesLength === 1 && !!branches[0]?.when?.length) || // 有一组数据并且when有值
|
||||
// (branchesLength > 1 && !branches[branchesLength - 1]?.when?.length)) // 有多组否则数据,并且最后一组when有值
|
||||
// ) {
|
||||
// branches.push(null);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// this.data = {
|
||||
// ...result,
|
||||
// trigger: result.trigger || {},
|
||||
// branches: cloneDeep(assignmentKey(branches)),
|
||||
// options: {...defaultOptions, ...result.options },
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// getProduct() {
|
||||
//
|
||||
// }
|
||||
// },
|
||||
// getters: {
|
||||
//
|
||||
// }
|
||||
// })
|
|
@ -1,5 +1,6 @@
|
|||
import type { Slots } from 'vue'
|
||||
import { TOKEN_KEY } from '@/utils/variable'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
/**
|
||||
* 静态图片资源处理
|
||||
|
@ -95,4 +96,16 @@ export const modifySearchColumnValue = (e: any, column: object) => {
|
|||
});
|
||||
});
|
||||
return e;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 仅提示一次的message
|
||||
* @param msg 消息内容
|
||||
* @param type 消息类型
|
||||
*/
|
||||
export const onlyMessage = (msg: string, type: 'success' | 'error' | 'warning' = 'success') => {
|
||||
message[type]({
|
||||
content: msg,
|
||||
key: type
|
||||
})
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* 座机号+手机号校验
|
||||
* @param value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const phoneRegEx = (value: string) => {
|
||||
const phone = new RegExp('^(((\\+86)|(\\+86-))|((86)|(86\\-))|((0086)|(0086\\-)))?1[3|5|7|8]\\d{9}$')
|
||||
const mobile = /(0[0-9]{2,3})([2-9][0-9]{6,7})+([0-9]{8,11})?$/
|
||||
return phone.test(value) || mobile.test(value)
|
||||
}
|
|
@ -226,11 +226,11 @@
|
|||
:key="index"
|
||||
:header="
|
||||
item.productKey
|
||||
? aliyunProductList.find(
|
||||
? (aliyunProductList.find(
|
||||
(i) =>
|
||||
i.productKey ===
|
||||
item.productKey,
|
||||
)?.productName
|
||||
)?.productName || `产品映射${index + 1}`)
|
||||
: `产品映射${index + 1}`
|
||||
"
|
||||
>
|
||||
|
@ -356,12 +356,14 @@
|
|||
</a-row>
|
||||
</a-form>
|
||||
<div v-if="type === 'edit'">
|
||||
<a-button
|
||||
:loading="loading"
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="saveBtn"
|
||||
>保存</a-button
|
||||
:hasPermission="['Northbound/AliCloud:add', 'Northbound/AliCloud:update']"
|
||||
>
|
||||
保存
|
||||
</PermissionButton>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
|
@ -497,11 +499,11 @@ const saveBtn = () => {
|
|||
.then(async (data: any) => {
|
||||
const product = (aliyunProductList.value || []).find(
|
||||
(item: any) =>
|
||||
item?.bridgeProductKey === data?.bridgeProductKey,
|
||||
item?.productKey === data?.bridgeProductKey,
|
||||
);
|
||||
data.bridgeProductName = product?.productName || '';
|
||||
loading.value = true;
|
||||
const resp = await savePatch(toRaw(modelRef));
|
||||
const resp = await savePatch({...toRaw(modelRef), ...data});
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Search :columns="columns" target="northbound-dueros" @search="handleSearch" />
|
||||
<Search :columns="columns" target="northbound-aliyun" @search="handleSearch" />
|
||||
<JTable
|
||||
ref="instanceRef"
|
||||
:columns="columns"
|
||||
|
@ -10,7 +10,14 @@
|
|||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleAdd">新增</a-button>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
@click="handleAdd"
|
||||
hasPermission="Northbound/AliCloud:add"
|
||||
>
|
||||
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||
新增
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
|
@ -57,42 +64,22 @@
|
|||
</a-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<a-tooltip
|
||||
v-bind="item.tooltip"
|
||||
:title="item.disabled && item.tooltip.title"
|
||||
<PermissionButton
|
||||
:disabled="item.disabled"
|
||||
:popConfirm="item.popConfirm"
|
||||
:tooltip="item.tooltip"
|
||||
@click="item.onClick"
|
||||
:hasPermission="'Northbound/AliCloud:' + item.key"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="item.popConfirm"
|
||||
v-bind="item.popConfirm"
|
||||
:disabled="item.disabled"
|
||||
>
|
||||
<a-button :disabled="item.disabled">
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<a-button
|
||||
:disabled="item.disabled"
|
||||
@click="item.onClick"
|
||||
>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
|
@ -103,38 +90,23 @@
|
|||
/>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
<a-space>
|
||||
<template
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
<PermissionButton
|
||||
:disabled="i.disabled"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
:popConfirm="i.popConfirm"
|
||||
:tooltip="i.tooltip"
|
||||
style="padding: 0px"
|
||||
@click="i.onClick"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
:hasPermission="'Northbound/AliCloud:' + i.key"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
@ -240,7 +212,7 @@ const getActions = (
|
|||
},
|
||||
},
|
||||
{
|
||||
key: 'edit',
|
||||
key: 'update',
|
||||
text: '编辑',
|
||||
tooltip: {
|
||||
title: '编辑',
|
||||
|
@ -257,7 +229,7 @@ const getActions = (
|
|||
title: data.state?.value !== 'disabled' ? '禁用' : '启用',
|
||||
},
|
||||
icon:
|
||||
data.state.value !== 'notActive'
|
||||
data.state.value !== 'disabled'
|
||||
? 'StopOutlined'
|
||||
: 'CheckCircleOutlined',
|
||||
popConfirm: {
|
||||
|
|
|
@ -43,7 +43,11 @@
|
|||
]"
|
||||
>
|
||||
<a-select
|
||||
:disabled="type !== 'edit' && modelRef.id && modelRef.id !== ':id'"
|
||||
:disabled="
|
||||
type !== 'edit' &&
|
||||
modelRef.id &&
|
||||
modelRef.id !== ':id'
|
||||
"
|
||||
placeholder="请选择产品"
|
||||
v-model:value="modelRef.id"
|
||||
show-search
|
||||
|
@ -410,12 +414,14 @@
|
|||
</a-row>
|
||||
</a-form>
|
||||
<div v-if="type === 'edit'">
|
||||
<a-button
|
||||
:loading="loading"
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="saveBtn"
|
||||
>保存</a-button
|
||||
:hasPermission="['Northbound/DuerOS:add', 'Northbound/DuerOS:update']"
|
||||
>
|
||||
保存
|
||||
</PermissionButton>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
|
@ -616,8 +622,8 @@ const getTypesActions = (val: string) => {
|
|||
const saveBtn = async () => {
|
||||
const tasks: any[] = [];
|
||||
for (let i = 0; i < command.value.length; i++) {
|
||||
const res = await (command.value[i] as any)?.saveBtn()
|
||||
if(!res || (res?.errorFields && res.errorFields.length)) {
|
||||
const res = await (command.value[i] as any)?.saveBtn();
|
||||
if (!res || (res?.errorFields && res.errorFields.length)) {
|
||||
actionActiveKey.value.push(String(i));
|
||||
tasks.push(false);
|
||||
} else {
|
||||
|
@ -629,7 +635,7 @@ const saveBtn = async () => {
|
|||
.then(async (data: any) => {
|
||||
if (tasks.every((item) => item) && data) {
|
||||
loading.value = true;
|
||||
const resp = await savePatch(toRaw(modelRef));
|
||||
const resp = await savePatch(data);
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
|
@ -642,10 +648,16 @@ const saveBtn = async () => {
|
|||
const _arr = err.errorFields.map((item: any) => item.name);
|
||||
_arr.map((item: string | any[]) => {
|
||||
if (item.length >= 3) {
|
||||
if(item[0] === 'propertyMappings' && !propertyActiveKey.value.includes(item[1])){
|
||||
if (
|
||||
item[0] === 'propertyMappings' &&
|
||||
!propertyActiveKey.value.includes(item[1])
|
||||
) {
|
||||
propertyActiveKey.value.push(item[1]);
|
||||
}
|
||||
if(item[0] === 'actionMappings' && !actionActiveKey.value.includes(item[1])){
|
||||
if (
|
||||
item[0] === 'actionMappings' &&
|
||||
!actionActiveKey.value.includes(item[1])
|
||||
) {
|
||||
actionActiveKey.value.push(item[1]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,14 @@
|
|||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleAdd">新增</a-button>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
@click="handleAdd"
|
||||
hasPermission="Northbound/DuerOS:add"
|
||||
>
|
||||
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||
新增
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
|
@ -55,42 +62,24 @@
|
|||
</a-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<a-tooltip
|
||||
v-bind="item.tooltip"
|
||||
:title="item.disabled && item.tooltip.title"
|
||||
<PermissionButton
|
||||
:disabled="item.disabled"
|
||||
:popConfirm="item.popConfirm"
|
||||
:tooltip="{
|
||||
...item.tooltip,
|
||||
}"
|
||||
@click="item.onClick"
|
||||
:hasPermission="'Northbound/DuerOS:' + item.key"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="item.popConfirm"
|
||||
v-bind="item.popConfirm"
|
||||
:disabled="item.disabled"
|
||||
>
|
||||
<a-button :disabled="item.disabled">
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<a-button
|
||||
:disabled="item.disabled"
|
||||
@click="item.onClick"
|
||||
>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
|
@ -104,38 +93,25 @@
|
|||
{{ slotProps.applianceType.text }}
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
<a-space>
|
||||
<template
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
<PermissionButton
|
||||
:disabled="i.disabled"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
:popConfirm="i.popConfirm"
|
||||
:tooltip="{
|
||||
...i.tooltip,
|
||||
}"
|
||||
style="padding: 0px"
|
||||
@click="i.onClick"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
:hasPermission="'Northbound/DuerOS:' + i.key"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
@ -143,17 +119,24 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { query, _undeploy, _deploy, _delete, queryProductList, queryTypes } from '@/api/northbound/dueros';
|
||||
import {
|
||||
query,
|
||||
_undeploy,
|
||||
_deploy,
|
||||
_delete,
|
||||
queryProductList,
|
||||
queryTypes,
|
||||
} from '@/api/northbound/dueros';
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useMenuStore } from 'store/menu'
|
||||
import { useMenuStore } from 'store/menu';
|
||||
|
||||
const router = useRouter();
|
||||
const instanceRef = ref<Record<string, any>>({});
|
||||
const params = ref<Record<string, any>>({});
|
||||
const current = ref<Record<string, any>>({});
|
||||
const menuStory = useMenuStore()
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('enabled', 'success');
|
||||
|
@ -238,14 +221,14 @@ const columns = [
|
|||
* 新增
|
||||
*/
|
||||
const handleAdd = () => {
|
||||
menuStory.jumpPage('Northbound/DuerOS/Detail', { id: ':id'})
|
||||
menuStory.jumpPage('Northbound/DuerOS/Detail', { id: ':id' });
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
const handleView = (id: string) => {
|
||||
menuStory.jumpPage('Northbound/DuerOS/Detail', { id }, { type: 'view' })
|
||||
menuStory.jumpPage('Northbound/DuerOS/Detail', { id }, { type: 'view' });
|
||||
};
|
||||
|
||||
const getActions = (
|
||||
|
@ -266,14 +249,18 @@ const getActions = (
|
|||
},
|
||||
},
|
||||
{
|
||||
key: 'edit',
|
||||
key: 'update',
|
||||
text: '编辑',
|
||||
tooltip: {
|
||||
title: '编辑',
|
||||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
menuStory.jumpPage('Northbound/DuerOS/Detail', { id: data.id }, { type: 'edit' })
|
||||
menuStory.jumpPage(
|
||||
'Northbound/DuerOS/Detail',
|
||||
{ id: data.id },
|
||||
{ type: 'edit' },
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -283,7 +270,7 @@ const getActions = (
|
|||
title: data.state?.value !== 'disabled' ? '禁用' : '启用',
|
||||
},
|
||||
icon:
|
||||
data.state.value !== 'notActive'
|
||||
data.state.value !== 'disabled'
|
||||
? 'StopOutlined'
|
||||
: 'CheckCircleOutlined',
|
||||
popConfirm: {
|
||||
|
|
|
@ -14,60 +14,79 @@
|
|||
>
|
||||
<a-row :gutter="30">
|
||||
<a-col :span="15">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="func.table"
|
||||
:pagination="false"
|
||||
rowKey="id"
|
||||
>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<template v-if="column.dataIndex === 'type'">
|
||||
<span>{{ record.type }}</span>
|
||||
<a-tooltip v-if="record.type === 'object'">
|
||||
<template slot="title">
|
||||
请按照json格式输入
|
||||
</template>
|
||||
<a-form :ref="`${func.id}Ref`" :model="func">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="func.table"
|
||||
:pagination="false"
|
||||
rowKey="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template
|
||||
v-if="column.dataIndex === 'type'"
|
||||
>
|
||||
<span>{{ record.type }}</span>
|
||||
<a-tooltip
|
||||
v-if="record.type === 'object'"
|
||||
>
|
||||
<template slot="title">
|
||||
请按照json格式输入
|
||||
</template>
|
||||
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
:style="{
|
||||
marginLeft: '5px',
|
||||
cursor: 'help',
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
:style="{
|
||||
marginLeft: '5px',
|
||||
cursor: 'help',
|
||||
}"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template
|
||||
v-if="column.dataIndex === 'value'"
|
||||
>
|
||||
<a-form-item
|
||||
:name="['table', index, 'value']"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '',
|
||||
}"
|
||||
/>
|
||||
</a-tooltip>
|
||||
has-feedback
|
||||
>
|
||||
<ValueItem
|
||||
:ref="`valueItemRef${record.id}`"
|
||||
v-model:modelValue="
|
||||
record.value
|
||||
"
|
||||
:itemType="record.type"
|
||||
:options="
|
||||
record.type === 'enum'
|
||||
? (
|
||||
record?.options
|
||||
?.elements || []
|
||||
).map((item:any) => ({
|
||||
label: item.text,
|
||||
value: item.value,
|
||||
}))
|
||||
: record.type === 'boolean'
|
||||
? [
|
||||
{
|
||||
label: '是',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: '否',
|
||||
value: false,
|
||||
},
|
||||
]
|
||||
: undefined
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'value'">
|
||||
<ValueItem
|
||||
:ref="`valueItemRef${record.id}`"
|
||||
v-model:modelValue="record.value"
|
||||
:itemType="record.type"
|
||||
:options="
|
||||
record.type === 'enum'
|
||||
? (
|
||||
record?.options
|
||||
?.elements || []
|
||||
).map((item:any) => ({
|
||||
label: item.text,
|
||||
value: item.value,
|
||||
}))
|
||||
: record.type === 'boolean'
|
||||
? [
|
||||
{
|
||||
label: '是',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: '否',
|
||||
value: false,
|
||||
},
|
||||
]
|
||||
: undefined
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-table>
|
||||
</a-form>
|
||||
<div class="editor-btn">
|
||||
<a-space>
|
||||
<a-button
|
||||
|
@ -180,36 +199,46 @@ const newFunctions = computed(() => {
|
|||
* 执行
|
||||
*/
|
||||
const handleExecute = async (func: any) => {
|
||||
const obj = {};
|
||||
func.table.forEach((item: any) => {
|
||||
if (item.type === 'object') {
|
||||
obj[item.id] = JSON.parse(item.value);
|
||||
} else {
|
||||
obj[item.id] = item.value;
|
||||
}
|
||||
});
|
||||
const { success, result } = await execute(
|
||||
route.params.id as string,
|
||||
func.id,
|
||||
obj,
|
||||
);
|
||||
if (!success) return;
|
||||
message.success('操作成功');
|
||||
func.executeResult = result instanceof Array ? result[0] : result;
|
||||
proxy?.$forceUpdate();
|
||||
proxy?.$refs[`${func.id}Ref`][0]
|
||||
.validate()
|
||||
.then(async () => {
|
||||
const obj = {};
|
||||
func.table.forEach((item: any) => {
|
||||
if (item.type === 'object') {
|
||||
obj[item.id] = JSON.parse(item.value);
|
||||
} else {
|
||||
obj[item.id] = item.value;
|
||||
}
|
||||
});
|
||||
const { success, result } = await execute(
|
||||
route.params.id as string,
|
||||
func.id,
|
||||
obj,
|
||||
);
|
||||
if (!success) return;
|
||||
message.success('操作成功');
|
||||
func.executeResult = result instanceof Array ? result[0] : result;
|
||||
proxy?.$forceUpdate();
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.log('err: ', err);
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 清空
|
||||
*/
|
||||
const handleClear = (func: any) => {
|
||||
func.table.forEach((item: any) => {
|
||||
item.value = undefined;
|
||||
proxy.$refs[`valueItemRef${item.id}`][0].myValue = undefined;
|
||||
});
|
||||
proxy?.$refs[`${func.id}Ref`][0].resetFields();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-table-cell .ant-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
:deep(.ant-form-item-with-help .ant-form-item-explain) {
|
||||
min-height: 0;
|
||||
}
|
||||
.wrapper {
|
||||
.tips {
|
||||
margin-bottom: 10px;
|
||||
|
|
|
@ -1,90 +1,153 @@
|
|||
<template>
|
||||
<div style="margin-top: 20px" v-if="config.length">
|
||||
<div style="display: flex; margin-bottom: 20px; align-items: center;">
|
||||
<div style="display: flex; margin-bottom: 20px; align-items: center">
|
||||
<div style="font-size: 16px; font-weight: 700">配置</div>
|
||||
<a-space>
|
||||
<a-button type="link" @click="visible = true"><AIcon type="EditOutlined" />编辑</a-button>
|
||||
<a-popconfirm title="确认重新应用该配置?" @confirm="deployBtn">
|
||||
<a-button type="link" v-if="instanceStore.detail.current?.value !== 'notActive'"><AIcon type="CheckOutlined" />应用配置<a-tooltip title="修改配置后需重新应用后才能生效。"><AIcon type="QuestionCircleOutlined" /></a-tooltip></a-button>
|
||||
</a-popconfirm>
|
||||
<a-popconfirm title="确认恢复默认配置?" @confirm="resetBtn">
|
||||
<a-button type="link" v-if="instanceStore.detail.aloneConfiguration"><AIcon type="SyncOutlined" />恢复默认<a-tooltip title="该设备单独编辑过配置信息,点击此将恢复成默认的配置信息,请谨慎操作。"><AIcon type="QuestionCircleOutlined" /></a-tooltip></a-button>
|
||||
</a-popconfirm>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
@click="visible = true"
|
||||
hasPermission="device/Instance:update"
|
||||
>
|
||||
<template #icon><AIcon type="EditOutlined" /></template>
|
||||
编辑
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
v-if="instanceStore.detail.current?.value !== 'notActive'"
|
||||
:popConfirm="{
|
||||
title: '确认重新应用该配置?',
|
||||
onConfirm: deployBtn,
|
||||
}"
|
||||
hasPermission="device/Instance:update"
|
||||
>
|
||||
<AIcon type="CheckOutlined" />应用配置<a-tooltip
|
||||
title="修改配置后需重新应用后才能生效。"
|
||||
><AIcon type="QuestionCircleOutlined"
|
||||
/></a-tooltip>
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
v-if="instanceStore.detail.aloneConfiguration"
|
||||
:popConfirm="{
|
||||
title: '确认恢复默认配置?',
|
||||
onConfirm: resetBtn,
|
||||
}"
|
||||
hasPermission="device/Instance:update"
|
||||
>
|
||||
<AIcon type="SyncOutlined" />恢复默认<a-tooltip
|
||||
title="该设备单独编辑过配置信息,点击此将恢复成默认的配置信息,请谨慎操作。"
|
||||
><AIcon type="QuestionCircleOutlined"
|
||||
/></a-tooltip>
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-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 v-for="item in i.properties" :key="item.property">
|
||||
<template #title
|
||||
><h4 style="font-size: 15px">{{ i.name }}</h4></template
|
||||
>
|
||||
<a-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"><AIcon type="QuestionCircleOutlined" /></a-tooltip>
|
||||
<span style="margin-right: 5px">{{ item.name }}</span>
|
||||
<a-tooltip v-if="item.description" :title="item.description"
|
||||
><AIcon type="QuestionCircleOutlined"
|
||||
/></a-tooltip>
|
||||
</template>
|
||||
<span v-if="item.type.type === 'password' && instanceStore.current?.configuration?.[item.property]?.length > 0">******</span>
|
||||
<span
|
||||
v-if="
|
||||
item.type.type === 'password' &&
|
||||
instanceStore.current?.configuration?.[item.property]
|
||||
?.length > 0
|
||||
"
|
||||
>******</span
|
||||
>
|
||||
<span v-else>
|
||||
<span>{{ instanceStore.current?.configuration?.[item.property] || '' }}</span>
|
||||
<a-tooltip v-if="isExit(item.property)" :title="`有效值:${instanceStore.current?.configuration?.[item.property]}`"><AIcon type="QuestionCircleOutlined" /></a-tooltip>
|
||||
<span>{{
|
||||
instanceStore.current?.configuration?.[item.property] ||
|
||||
''
|
||||
}}</span>
|
||||
<a-tooltip
|
||||
v-if="isExit(item.property)"
|
||||
:title="`有效值:${
|
||||
instanceStore.current?.configuration?.[
|
||||
item.property
|
||||
]
|
||||
}`"
|
||||
><AIcon type="QuestionCircleOutlined"
|
||||
/></a-tooltip>
|
||||
</span>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
<Save v-if="visible" @save="saveBtn" @close="visible = false" :config="config" />
|
||||
<Save
|
||||
v-if="visible"
|
||||
@save="saveBtn"
|
||||
@close="visible = false"
|
||||
:config="config"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useInstanceStore } from "@/store/instance"
|
||||
import { ConfigMetadata } from "@/views/device/Product/typings"
|
||||
import { getConfigMetadata, _deploy, configurationReset } from '@/api/device/instance'
|
||||
import { message } from "ant-design-vue"
|
||||
import Save from './Save.vue'
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import { ConfigMetadata } from '@/views/device/Product/typings';
|
||||
import {
|
||||
getConfigMetadata,
|
||||
_deploy,
|
||||
configurationReset,
|
||||
} from '@/api/device/instance';
|
||||
import { message } from 'ant-design-vue';
|
||||
import Save from './Save.vue';
|
||||
|
||||
const instanceStore = useInstanceStore()
|
||||
const visible = ref<boolean>(false)
|
||||
const config = ref<ConfigMetadata[]>([])
|
||||
const instanceStore = useInstanceStore();
|
||||
const visible = ref<boolean>(false);
|
||||
const config = ref<ConfigMetadata[]>([]);
|
||||
|
||||
watchEffect(() => {
|
||||
if(instanceStore.current.id){
|
||||
getConfigMetadata(instanceStore.current.id).then(resp => {
|
||||
if(resp.status === 200){
|
||||
config.value = resp?.result as ConfigMetadata[]
|
||||
if (instanceStore.current.id) {
|
||||
getConfigMetadata(instanceStore.current.id).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
config.value = resp?.result as ConfigMetadata[];
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const isExit = (property: string) => {
|
||||
return (
|
||||
instanceStore.current?.cachedConfiguration &&
|
||||
instanceStore.current?.cachedConfiguration[property] !== undefined &&
|
||||
instanceStore.current?.configuration &&
|
||||
instanceStore.current?.configuration[property] !==
|
||||
instanceStore.current?.cachedConfiguration[property]
|
||||
instanceStore.current?.cachedConfiguration &&
|
||||
instanceStore.current?.cachedConfiguration[property] !== undefined &&
|
||||
instanceStore.current?.configuration &&
|
||||
instanceStore.current?.configuration[property] !==
|
||||
instanceStore.current?.cachedConfiguration[property]
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const deployBtn = async () => {
|
||||
if(instanceStore.current.id){
|
||||
const resp = await _deploy(instanceStore.current.id)
|
||||
if (instanceStore.current.id) {
|
||||
const resp = await _deploy(instanceStore.current.id);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功')
|
||||
instanceStore.refresh(instanceStore.current.id)
|
||||
message.success('操作成功');
|
||||
instanceStore.refresh(instanceStore.current.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const resetBtn = async () => {
|
||||
if(instanceStore.current.id){
|
||||
const resp = await configurationReset(instanceStore.current.id)
|
||||
if (instanceStore.current.id) {
|
||||
const resp = await configurationReset(instanceStore.current.id);
|
||||
if (resp.status === 200) {
|
||||
message.success('恢复默认配置成功')
|
||||
instanceStore.refresh(instanceStore.current.id)
|
||||
message.success('恢复默认配置成功');
|
||||
instanceStore.refresh(instanceStore.current.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const saveBtn = () => {
|
||||
visible.value = false
|
||||
if(instanceStore.current.id){
|
||||
instanceStore.refresh(instanceStore.current.id)
|
||||
visible.value = false;
|
||||
if (instanceStore.current.id) {
|
||||
instanceStore.refresh(instanceStore.current.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -3,31 +3,50 @@
|
|||
<a-descriptions bordered>
|
||||
<template #title>
|
||||
关系信息
|
||||
<a-button type="link" @click="visible = true"><AIcon type="EditOutlined" />编辑<a-tooltip title="管理设备与其他业务的关联关系,关系来源于关系配置"><AIcon type="QuestionCircleOutlined" /></a-tooltip></a-button>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
@click="visible = true"
|
||||
hasPermission="device/Instance:update"
|
||||
>
|
||||
<AIcon type="EditOutlined" />编辑<a-tooltip
|
||||
title="管理设备与其他业务的关联关系,关系来源于关系配置"
|
||||
><AIcon type="QuestionCircleOutlined"
|
||||
/></a-tooltip>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
<a-descriptions-item :span="1" v-for="item in dataSource" :key="item.objectId" :label="item.relationName">{{ item?.related ? (item?.related || []).map(i => i.name).join(',') : '' }}</a-descriptions-item>
|
||||
<a-descriptions-item
|
||||
:span="1"
|
||||
v-for="item in dataSource"
|
||||
:key="item.objectId"
|
||||
:label="item.relationName"
|
||||
>{{
|
||||
item?.related
|
||||
? (item?.related || []).map((i) => i.name).join(',')
|
||||
: ''
|
||||
}}</a-descriptions-item
|
||||
>
|
||||
</a-descriptions>
|
||||
<Save v-if="visible" @save="saveBtn" @close="visible = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useInstanceStore } from "@/store/instance"
|
||||
import Save from './Save.vue'
|
||||
const instanceStore = useInstanceStore()
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import Save from './Save.vue';
|
||||
const instanceStore = useInstanceStore();
|
||||
|
||||
const dataSource = ref<Record<any, any>[]>([])
|
||||
const dataSource = ref<Record<any, any>[]>([]);
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
watchEffect(() => {
|
||||
const arr = (instanceStore.current?.relations || []).reverse()
|
||||
dataSource.value = arr as Record<any, any>[]
|
||||
})
|
||||
const arr = (instanceStore.current?.relations || []).reverse();
|
||||
dataSource.value = arr as Record<any, any>[];
|
||||
});
|
||||
|
||||
const saveBtn = () => {
|
||||
visible.value = false
|
||||
if(instanceStore.current.id){
|
||||
instanceStore.refresh(instanceStore.current.id)
|
||||
visible.value = false;
|
||||
if (instanceStore.current.id) {
|
||||
instanceStore.refresh(instanceStore.current.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -3,32 +3,44 @@
|
|||
<a-descriptions bordered>
|
||||
<template #title>
|
||||
标签
|
||||
<a-button type="link" @click="visible = true"><AIcon type="EditOutlined" />编辑</a-button>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
@click="visible = true"
|
||||
hasPermission="device/Instance:update"
|
||||
>
|
||||
<AIcon type="EditOutlined" />编辑
|
||||
</PermissionButton>
|
||||
</template>
|
||||
<a-descriptions-item :span="1" v-for="item in dataSource" :key="item.key" :label="`${item.name}(${item.key})`">{{ item?.value }}</a-descriptions-item>
|
||||
<a-descriptions-item
|
||||
:span="1"
|
||||
v-for="item in dataSource"
|
||||
:key="item.key"
|
||||
:label="`${item.name}(${item.key})`"
|
||||
>{{ item?.value }}</a-descriptions-item
|
||||
>
|
||||
</a-descriptions>
|
||||
<Save v-if="visible" @close="visible = false" @save="saveBtn" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useInstanceStore } from "@/store/instance"
|
||||
import Save from './Save.vue'
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import Save from './Save.vue';
|
||||
|
||||
const instanceStore = useInstanceStore()
|
||||
const instanceStore = useInstanceStore();
|
||||
|
||||
const dataSource = ref<Record<any, any>[]>([])
|
||||
const visible = ref<boolean>(false)
|
||||
const dataSource = ref<Record<any, any>[]>([]);
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
watchEffect(() => {
|
||||
const arr = (instanceStore.current?.tags || [])
|
||||
dataSource.value = arr as Record<any, any>[]
|
||||
})
|
||||
const arr = instanceStore.current?.tags || [];
|
||||
dataSource.value = arr as Record<any, any>[];
|
||||
});
|
||||
|
||||
const saveBtn = () => {
|
||||
visible.value = false
|
||||
if(instanceStore.current.id){
|
||||
instanceStore.refresh(instanceStore.current.id)
|
||||
visible.value = false;
|
||||
if (instanceStore.current.id) {
|
||||
instanceStore.refresh(instanceStore.current.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -3,43 +3,105 @@
|
|||
<a-descriptions bordered>
|
||||
<template #title>
|
||||
设备信息
|
||||
<a-button type="link" @click="visible = true"><AIcon type="EditOutlined" />编辑</a-button>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
@click="visible = true"
|
||||
hasPermission="device/Instance:update"
|
||||
>
|
||||
<template #icon><AIcon type="EditOutlined" /></template>
|
||||
编辑
|
||||
</PermissionButton>
|
||||
</template>
|
||||
<a-descriptions-item label="设备ID">{{ instanceStore.current.id }}</a-descriptions-item>
|
||||
<a-descriptions-item label="产品名称">{{ instanceStore.current.productName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="产品分类">{{ instanceStore.current.classifiedName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="设备类型">{{ instanceStore.current.deviceType?.text }}</a-descriptions-item>
|
||||
<a-descriptions-item label="固件版本">{{ instanceStore.current.firmwareInfo?.version }}</a-descriptions-item>
|
||||
<a-descriptions-item label="连接协议">{{ instanceStore.current.protocolName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="消息协议">{{ instanceStore.current.transport }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ instanceStore.current.createTime ? moment(instanceStore.current.createTime).format('YYYY-MM-DD HH:mm:ss') : '' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="注册时间">{{ instanceStore.current.registerTime ? moment(instanceStore.current.registerTime).format('YYYY-MM-DD HH:mm:ss') : ''}}</a-descriptions-item>
|
||||
<a-descriptions-item label="最后上线时间">{{ instanceStore.current.onlineTime ? moment(instanceStore.current.onlineTime).format('YYYY-MM-DD HH:mm:ss') : '' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="父设备" v-if="instanceStore.current.deviceType?.value === 'childrenDevice'">{{ instanceStore.current.parentId }}</a-descriptions-item>
|
||||
<a-descriptions-item label="说明">{{ instanceStore.current.description }}</a-descriptions-item>
|
||||
<a-descriptions-item label="设备ID">{{
|
||||
instanceStore.current.id
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="产品名称">{{
|
||||
instanceStore.current.productName
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="产品分类">{{
|
||||
instanceStore.current.classifiedName
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="设备类型">{{
|
||||
instanceStore.current.deviceType?.text
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="固件版本">{{
|
||||
instanceStore.current.firmwareInfo?.version
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="连接协议">{{
|
||||
instanceStore.current.protocolName
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="消息协议">{{
|
||||
instanceStore.current.transport
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{
|
||||
instanceStore.current.createTime
|
||||
? moment(instanceStore.current.createTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
: ''
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="注册时间">{{
|
||||
instanceStore.current.registerTime
|
||||
? moment(instanceStore.current.registerTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
: ''
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="最后上线时间">{{
|
||||
instanceStore.current.onlineTime
|
||||
? moment(instanceStore.current.onlineTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
: ''
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item
|
||||
label="父设备"
|
||||
v-if="
|
||||
instanceStore.current.deviceType?.value === 'childrenDevice'
|
||||
"
|
||||
>{{ instanceStore.current.parentId }}</a-descriptions-item
|
||||
>
|
||||
<a-descriptions-item label="说明">{{
|
||||
instanceStore.current.description
|
||||
}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
<Config />
|
||||
<Tags v-if="instanceStore.current?.tags && instanceStore.current?.tags.length > 0 " />
|
||||
<Relation v-if="instanceStore.current?.relations && instanceStore.current?.relations.length > 0" />
|
||||
<Save v-if="visible" :data="instanceStore.current" @close="visible = false" @save="saveBtn" />
|
||||
<Tags
|
||||
v-if="
|
||||
instanceStore.current?.tags &&
|
||||
instanceStore.current?.tags.length > 0
|
||||
"
|
||||
/>
|
||||
<Relation
|
||||
v-if="
|
||||
instanceStore.current?.relations &&
|
||||
instanceStore.current?.relations.length > 0
|
||||
"
|
||||
/>
|
||||
<Save
|
||||
v-if="visible"
|
||||
:data="instanceStore.current"
|
||||
@close="visible = false"
|
||||
@save="saveBtn"
|
||||
/>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useInstanceStore } from '@/store/instance'
|
||||
import Save from '../../Save/index.vue'
|
||||
import Config from './components/Config/index.vue'
|
||||
import Tags from './components/Tags/index.vue'
|
||||
import Relation from './components/Relation/index.vue'
|
||||
import moment from 'moment'
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import Save from '../../Save/index.vue';
|
||||
import Config from './components/Config/index.vue';
|
||||
import Tags from './components/Tags/index.vue';
|
||||
import Relation from './components/Relation/index.vue';
|
||||
import moment from 'moment';
|
||||
|
||||
const visible = ref<boolean>(false)
|
||||
const instanceStore = useInstanceStore()
|
||||
const visible = ref<boolean>(false);
|
||||
const instanceStore = useInstanceStore();
|
||||
|
||||
const saveBtn = () => {
|
||||
if(instanceStore.current?.id){
|
||||
instanceStore.refresh(instanceStore.current?.id)
|
||||
if (instanceStore.current?.id) {
|
||||
instanceStore.refresh(instanceStore.current?.id);
|
||||
}
|
||||
visible.value = false
|
||||
}
|
||||
visible.value = false;
|
||||
};
|
||||
</script>
|
|
@ -8,7 +8,8 @@
|
|||
<template #title>
|
||||
<div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<div>{{ instanceStore.current.name }}</div>
|
||||
<AIcon type="ArrowLeftOutlined" @click="onBack" />
|
||||
<div style="margin-left: 20px">{{ instanceStore.current.name }}</div>
|
||||
<a-divider type="vertical" />
|
||||
<a-space>
|
||||
<a-badge
|
||||
|
@ -19,25 +20,35 @@
|
|||
)
|
||||
"
|
||||
/>
|
||||
<a-popconfirm
|
||||
title="确认启用设备"
|
||||
@confirm="handleAction"
|
||||
<PermissionButton
|
||||
v-if="
|
||||
instanceStore.current.state?.value ===
|
||||
'notActive'
|
||||
"
|
||||
type="link"
|
||||
style="margin-top: -5px; padding: 0 20px"
|
||||
:popConfirm="{
|
||||
title: '确认启用设备',
|
||||
onConfirm: handleAction,
|
||||
}"
|
||||
hasPermission="device/Instance:action"
|
||||
>
|
||||
<a-button type="link">启用设备</a-button>
|
||||
</a-popconfirm>
|
||||
<a-popconfirm
|
||||
title="确认断开连接"
|
||||
@confirm="handleDisconnect"
|
||||
启用设备
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
v-if="
|
||||
instanceStore.current.state?.value === 'online'
|
||||
"
|
||||
type="link"
|
||||
style="margin-top: -5px; padding: 0 20px"
|
||||
:popConfirm="{
|
||||
title: '确认断开连接?',
|
||||
onConfirm: handleDisconnect,
|
||||
}"
|
||||
hasPermission="device/Instance:action"
|
||||
>
|
||||
<a-button type="link">断开连接</a-button>
|
||||
</a-popconfirm>
|
||||
断开连接
|
||||
</PermissionButton>
|
||||
<a-tooltip
|
||||
v-if="
|
||||
instanceStore.current?.accessProvider ===
|
||||
|
@ -66,14 +77,14 @@
|
|||
instanceStore.current.id
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="所属产品">
|
||||
<a-button
|
||||
style="margin-top: -5px; padding: 0"
|
||||
<PermissionButton
|
||||
type="link"
|
||||
style="margin-top: -5px; padding: 0"
|
||||
@click="jumpProduct"
|
||||
>{{
|
||||
instanceStore.current.productName
|
||||
}}</a-button
|
||||
hasPermission="device/Product:view"
|
||||
>
|
||||
{{ instanceStore.current.productName }}
|
||||
</PermissionButton>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
|
@ -109,6 +120,9 @@ import { _deploy, _disconnect } from '@/api/device/instance';
|
|||
import { message } from 'ant-design-vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { getWebSocket } from '@/utils/websocket';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const route = useRoute();
|
||||
const instanceStore = useInstanceStore();
|
||||
|
@ -180,7 +194,9 @@ watch(
|
|||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
const onBack = () => {};
|
||||
const onBack = () => {
|
||||
menuStory.jumpPage('device/Instance');
|
||||
};
|
||||
|
||||
const onTabChange = (e: string) => {
|
||||
instanceStore.tabActiveKey = e;
|
||||
|
@ -214,12 +230,18 @@ const handleRefresh = async () => {
|
|||
};
|
||||
|
||||
const jumpProduct = () => {
|
||||
message.warn('暂未开发');
|
||||
menuStory.jumpPage('device/Product/Detail', {
|
||||
id: instanceStore.current.productId,
|
||||
});
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
const keys = list.value.map((i) => i.key);
|
||||
if (instanceStore.current.protocol && !(['modbus-tcp', 'opc-ua'].includes(instanceStore.current.protocol)) && !keys.includes('Diagnose')) {
|
||||
if (
|
||||
instanceStore.current.protocol &&
|
||||
!['modbus-tcp', 'opc-ua'].includes(instanceStore.current.protocol) &&
|
||||
!keys.includes('Diagnose')
|
||||
) {
|
||||
list.value.push({
|
||||
key: 'Diagnose',
|
||||
tab: '设备诊断',
|
||||
|
|
|
@ -19,7 +19,14 @@
|
|||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleAdd">新增</a-button>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
@click="handleAdd"
|
||||
hasPermission="device/Instance:add"
|
||||
>
|
||||
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||
新增
|
||||
</PermissionButton>
|
||||
<a-dropdown>
|
||||
<a-button
|
||||
>批量操作 <AIcon type="DownOutlined"
|
||||
|
@ -27,77 +34,101 @@
|
|||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item>
|
||||
<a-button @click="exportVisible = true"
|
||||
><AIcon
|
||||
type="ExportOutlined"
|
||||
/>批量导出设备</a-button
|
||||
<PermissionButton
|
||||
@click="exportVisible = true"
|
||||
hasPermission="device/Instance:export"
|
||||
>
|
||||
<template #icon
|
||||
><AIcon type="ExportOutlined"
|
||||
/></template>
|
||||
批量导出设备
|
||||
</PermissionButton>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<a-button @click="importVisible = true"
|
||||
><AIcon
|
||||
type="ImportOutlined"
|
||||
/>批量导入设备</a-button
|
||||
<PermissionButton
|
||||
@click="importVisible = true"
|
||||
hasPermission="device/Instance:import"
|
||||
>
|
||||
<template #icon
|
||||
><AIcon type="ImportOutlined"
|
||||
/></template>
|
||||
批量导入设备
|
||||
</PermissionButton>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<a-popconfirm
|
||||
@confirm="activeAllDevice"
|
||||
title="确认激活全部设备?"
|
||||
>
|
||||
<a-button type="primary" ghost
|
||||
><AIcon
|
||||
type="CheckCircleOutlined"
|
||||
/>激活全部设备</a-button
|
||||
>
|
||||
</a-popconfirm>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<a-button
|
||||
@click="syncDeviceStatus"
|
||||
<PermissionButton
|
||||
ghost
|
||||
type="primary"
|
||||
><AIcon
|
||||
type="SyncOutlined"
|
||||
/>同步设备状态</a-button
|
||||
:popConfirm="{
|
||||
title: '确认激活全部设备?',
|
||||
onConfirm: activeAllDevice,
|
||||
}"
|
||||
hasPermission="device/Instance:action"
|
||||
>
|
||||
<template #icon
|
||||
><AIcon type="CheckCircleOutlined"
|
||||
/></template>
|
||||
激活全部设备
|
||||
</PermissionButton>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
@click="syncDeviceStatus"
|
||||
hasPermission="device/Instance:view"
|
||||
>
|
||||
<template #icon
|
||||
><AIcon type="SyncOutlined"
|
||||
/></template>
|
||||
同步设备状态
|
||||
</PermissionButton>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="_selectedRowKeys.length">
|
||||
<a-popconfirm
|
||||
@confirm="delSelectedDevice"
|
||||
title="已启用的设备无法删除,确认删除选中的禁用状态设备?"
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
danger
|
||||
:popConfirm="{
|
||||
title: '已启用的设备无法删除,确认删除选中的禁用状态设备?',
|
||||
onConfirm: delSelectedDevice,
|
||||
}"
|
||||
hasPermission="device/Instance:delete"
|
||||
>
|
||||
<a-button type="primary" danger
|
||||
><AIcon
|
||||
type="DeleteOutlined"
|
||||
/>删除选中设备</a-button
|
||||
>
|
||||
</a-popconfirm>
|
||||
</a-menu-item>
|
||||
<a-menu-item
|
||||
v-if="_selectedRowKeys.length"
|
||||
title="确认激活选中设备?"
|
||||
>
|
||||
<a-popconfirm
|
||||
@confirm="activeSelectedDevice"
|
||||
>
|
||||
<a-button type="primary"
|
||||
><AIcon
|
||||
type="CheckOutlined"
|
||||
/>激活选中设备</a-button
|
||||
>
|
||||
</a-popconfirm>
|
||||
<template #icon
|
||||
><AIcon type="DeleteOutlined"
|
||||
/></template>
|
||||
删除选中设备
|
||||
</PermissionButton>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="_selectedRowKeys.length">
|
||||
<a-popconfirm
|
||||
@confirm="disabledSelectedDevice"
|
||||
title="确认禁用选中设备?"
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:popConfirm="{
|
||||
title: '确认激活选中设备',
|
||||
onConfirm: activeSelectedDevice,
|
||||
}"
|
||||
hasPermission="device/Instance:action"
|
||||
>
|
||||
<a-button type="primary" danger
|
||||
><AIcon
|
||||
type="StopOutlined"
|
||||
/>禁用选中设备</a-button
|
||||
>
|
||||
</a-popconfirm>
|
||||
<template #icon
|
||||
><AIcon type="CheckOutlined"
|
||||
/></template>
|
||||
激活选中设备
|
||||
</PermissionButton>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="_selectedRowKeys.length">
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
danger
|
||||
:popConfirm="{
|
||||
title: '确认禁用选中设备?',
|
||||
onConfirm: disabledSelectedDevice,
|
||||
}"
|
||||
hasPermission="device/Instance:action"
|
||||
>
|
||||
<template #icon
|
||||
><AIcon type="StopOutlined"
|
||||
/></template>
|
||||
禁用选中设备
|
||||
</PermissionButton>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
|
@ -151,42 +182,24 @@
|
|||
</a-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<a-tooltip
|
||||
v-bind="item.tooltip"
|
||||
:title="item.disabled && item.tooltip.title"
|
||||
<PermissionButton
|
||||
:disabled="item.disabled"
|
||||
:popConfirm="item.popConfirm"
|
||||
:tooltip="{
|
||||
...item.tooltip,
|
||||
}"
|
||||
@click="item.onClick"
|
||||
:hasPermission="'device/Instance:' + item.key"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="item.popConfirm"
|
||||
v-bind="item.popConfirm"
|
||||
:disabled="item.disabled"
|
||||
>
|
||||
<a-button :disabled="item.disabled">
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<a-button
|
||||
:disabled="item.disabled"
|
||||
@click="item.onClick"
|
||||
>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
|
@ -197,38 +210,25 @@
|
|||
/>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
<a-space>
|
||||
<template
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
<PermissionButton
|
||||
:disabled="i.disabled"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
:popConfirm="i.popConfirm"
|
||||
:tooltip="{
|
||||
...i.tooltip,
|
||||
}"
|
||||
@click="i.onClick"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
style="padding: 0px"
|
||||
:hasPermission="'device/Instance:' + i.key"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
@ -278,6 +278,7 @@ import {
|
|||
queryOrgThree,
|
||||
} from '@/api/device/product';
|
||||
import { queryTree } from '@/api/device/category';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
|
||||
const router = useRouter();
|
||||
const instanceRef = ref<Record<string, any>>({});
|
||||
|
@ -291,6 +292,8 @@ const operationVisible = ref<boolean>(false);
|
|||
const api = ref<string>('');
|
||||
const type = ref<string>('');
|
||||
|
||||
const menuStory = useMenuStore()
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('online', 'success');
|
||||
statusMap.set('offline', 'error');
|
||||
|
@ -535,7 +538,7 @@ const handleAdd = () => {
|
|||
* 查看
|
||||
*/
|
||||
const handleView = (id: string) => {
|
||||
router.push('/iot/device/instance/detail/' + id);
|
||||
menuStory.jumpPage('device/Instance/Detail', {id})
|
||||
};
|
||||
|
||||
const getActions = (
|
||||
|
@ -556,7 +559,7 @@ const getActions = (
|
|||
},
|
||||
},
|
||||
{
|
||||
key: 'edit',
|
||||
key: 'update',
|
||||
text: '编辑',
|
||||
tooltip: {
|
||||
title: '编辑',
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<a-card class="device-product">
|
||||
<Search
|
||||
:columns="query.columns"
|
||||
target="product-manage"
|
||||
|
@ -163,7 +162,6 @@
|
|||
:title="title"
|
||||
@success="refresh"
|
||||
/>
|
||||
</a-card>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -258,12 +258,11 @@
|
|||
placeholder="请输入说明"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{ offset: 0, span: 3 }">
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleSubmit"
|
||||
:loading="btnLoading"
|
||||
style="width: 100%"
|
||||
>
|
||||
保存
|
||||
</a-button>
|
||||
|
|
|
@ -329,8 +329,6 @@ const getActions = (
|
|||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
// visible.value = true;
|
||||
// current.value = data;
|
||||
menuStory.jumpPage('notice/Config/Detail', {
|
||||
id: data.id,
|
||||
});
|
||||
|
|
|
@ -8,8 +8,12 @@
|
|||
@cancel="handleCancel"
|
||||
:confirmLoading="btnLoading"
|
||||
>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="通知配置" v-bind="validateInfos.configId">
|
||||
<a-form ref="formRef" layout="vertical" :model="formData">
|
||||
<a-form-item
|
||||
label="通知配置"
|
||||
name="configId"
|
||||
:rules="{ required: true, message: '该字段为必填字段' }"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="formData.configId"
|
||||
placeholder="请选择通知配置"
|
||||
|
@ -25,32 +29,56 @@
|
|||
</a-form-item>
|
||||
<a-form-item
|
||||
label="变量"
|
||||
v-bind="validateInfos.variableDefinitions"
|
||||
v-if="templateDetailTable && templateDetailTable.length"
|
||||
v-if="
|
||||
formData.templateDetailTable &&
|
||||
formData.templateDetailTable.length
|
||||
"
|
||||
>
|
||||
<a-table
|
||||
ref="myTable"
|
||||
class="debug-table"
|
||||
row-key="id"
|
||||
:columns="columns"
|
||||
:data-source="templateDetailTable"
|
||||
:data-source="formData.templateDetailTable"
|
||||
:pagination="false"
|
||||
:rowKey="
|
||||
(record, index) => {
|
||||
return record.id;
|
||||
}
|
||||
"
|
||||
bordered
|
||||
>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template
|
||||
v-if="['id', 'name'].includes(column.dataIndex)"
|
||||
>
|
||||
<span>{{ record[column.dataIndex] }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ValueItem
|
||||
v-model:modelValue="record.value"
|
||||
:itemType="record.type"
|
||||
/>
|
||||
<a-form-item
|
||||
:name="['templateDetailTable', index, 'value']"
|
||||
:rules="{
|
||||
required: record.required,
|
||||
message: '该字段为必填字段',
|
||||
}"
|
||||
>
|
||||
<ToUser
|
||||
v-if="record.type === 'user'"
|
||||
v-model:toUser="record.value"
|
||||
:type="data.type"
|
||||
:config-id="formData.configId"
|
||||
/>
|
||||
<ToOrg
|
||||
v-else-if="record.type === 'org'"
|
||||
:type="data.type"
|
||||
:config-id="formData.configId"
|
||||
v-model:toParty="record.value"
|
||||
/>
|
||||
<ToTag
|
||||
v-else-if="record.type === 'tag'"
|
||||
:type="data.type"
|
||||
:config-id="formData.configId"
|
||||
v-model:toTag="record.value"
|
||||
/>
|
||||
<ValueItem
|
||||
v-else
|
||||
v-model:modelValue="record.value"
|
||||
:itemType="record.type"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
@ -60,7 +88,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Form } from 'ant-design-vue';
|
||||
import { PropType } from 'vue';
|
||||
import TemplateApi from '@/api/notice/template';
|
||||
import type {
|
||||
|
@ -70,7 +97,9 @@ import type {
|
|||
} from '@/views/notice/Template/types';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
import ToUser from '../Detail/components/ToUser.vue';
|
||||
import ToOrg from '../Detail/components/ToOrg.vue';
|
||||
import ToTag from '../Detail/components/ToTag.vue';
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:visible', data: boolean): void;
|
||||
|
@ -103,6 +132,8 @@ const getConfigList = async () => {
|
|||
};
|
||||
const { result } = await TemplateApi.getConfig(params);
|
||||
configList.value = result;
|
||||
// 设置默认配置
|
||||
if (configList.value.length) formData.value.configId = props.data.configId;
|
||||
};
|
||||
|
||||
watch(
|
||||
|
@ -118,13 +149,15 @@ watch(
|
|||
/**
|
||||
* 获取模板详情
|
||||
*/
|
||||
const templateDetailTable = ref<IVariableDefinitions[]>();
|
||||
const getTemplateDetail = async () => {
|
||||
const { result } = await TemplateApi.getTemplateDetail(props.data.id);
|
||||
templateDetailTable.value = result.variableDefinitions.map((m: any) => ({
|
||||
...m,
|
||||
value: undefined,
|
||||
}));
|
||||
formData.value.templateDetailTable = result.variableDefinitions.map(
|
||||
(m: any) => ({
|
||||
...m,
|
||||
type: m.expands ? m.expands.businessType : m.type,
|
||||
value: undefined,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
|
@ -147,31 +180,27 @@ const columns = [
|
|||
];
|
||||
|
||||
// 表单数据
|
||||
const formData = ref({
|
||||
const formData = ref<{
|
||||
configId: string;
|
||||
variableDefinitions: string;
|
||||
templateDetailTable: IVariableDefinitions[];
|
||||
}>({
|
||||
configId: '',
|
||||
variableDefinitions: '',
|
||||
templateDetailTable: [],
|
||||
});
|
||||
|
||||
// 验证规则
|
||||
const formRules = ref({
|
||||
configId: [{ required: true, message: '请选择通知模板' }],
|
||||
variableDefinitions: [{ required: false, message: '该字段是必填字段' }],
|
||||
});
|
||||
|
||||
const { resetFields, validate, validateInfos, clearValidate } = useForm(
|
||||
formData.value,
|
||||
formRules.value,
|
||||
);
|
||||
|
||||
/**
|
||||
* 提交
|
||||
*/
|
||||
const formRef = ref();
|
||||
const btnLoading = ref(false);
|
||||
const handleOk = () => {
|
||||
validate()
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
const params = {};
|
||||
templateDetailTable.value?.forEach((item) => {
|
||||
formData.value.templateDetailTable?.forEach((item) => {
|
||||
params[item.id] = item.value;
|
||||
});
|
||||
// console.log('params: ', params);
|
||||
|
@ -187,16 +216,20 @@ const handleOk = () => {
|
|||
btnLoading.value = false;
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch((err: any) => {
|
||||
console.log('err: ', err);
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
_vis.value = false;
|
||||
templateDetailTable.value = [];
|
||||
resetFields();
|
||||
formRef.value.resetFields();
|
||||
formData.value.templateDetailTable = [];
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-table-cell .ant-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -27,11 +27,17 @@ const _value = computed({
|
|||
get: () => props.toParty,
|
||||
set: (val: string) => emit('update:toParty', val),
|
||||
});
|
||||
|
||||
const typeObj = {
|
||||
weixin: 'wechat',
|
||||
dingTalk: 'dingtalk',
|
||||
};
|
||||
const options = ref([]);
|
||||
const queryData = async () => {
|
||||
if (!props.configId) return;
|
||||
const { result } = await templateApi.getDept(props.type, props.configId);
|
||||
const { result } = await templateApi.getDept(
|
||||
typeObj[props.type],
|
||||
props.configId,
|
||||
);
|
||||
options.value = result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
|
|
|
@ -28,10 +28,17 @@ const _value = computed({
|
|||
set: (val: string) => emit('update:toUser', val),
|
||||
});
|
||||
|
||||
const typeObj = {
|
||||
weixin: 'wechat',
|
||||
dingTalk: 'dingtalk',
|
||||
};
|
||||
const options = ref([]);
|
||||
const queryData = async () => {
|
||||
if (!props.configId) return;
|
||||
const { result } = await templateApi.getUser(props.type, props.configId);
|
||||
const { result } = await templateApi.getUser(
|
||||
typeObj[props.type],
|
||||
props.configId,
|
||||
);
|
||||
options.value = result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
|
|
|
@ -157,6 +157,7 @@
|
|||
formData.template.messageType
|
||||
"
|
||||
placeholder="请选择消息类型"
|
||||
@change="handleMessageTypeChange"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(
|
||||
|
@ -480,7 +481,11 @@
|
|||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item>
|
||||
<a-form-item
|
||||
v-bind="
|
||||
validateInfos['template.calledShowNumbers']
|
||||
"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
被叫显号
|
||||
|
@ -713,12 +718,11 @@
|
|||
placeholder="请输入说明"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{ offset: 0, span: 3 }">
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleSubmit"
|
||||
:loading="btnLoading"
|
||||
style="width: 100%"
|
||||
>
|
||||
保存
|
||||
</a-button>
|
||||
|
@ -757,6 +761,8 @@ import ToTag from './components/ToTag.vue';
|
|||
import { FILE_UPLOAD } from '@/api/comm';
|
||||
import { LocalStore } from '@/utils/comm';
|
||||
import { TOKEN_KEY } from '@/utils/variable';
|
||||
import { phoneRegEx } from '@/utils/validate';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
@ -801,7 +807,7 @@ const resetPublicFiles = () => {
|
|||
formData.value.configId = undefined;
|
||||
|
||||
if (
|
||||
formData.value.type === 'dingTalk' ||
|
||||
formData.value.provider === 'dingTalkMessage' ||
|
||||
formData.value.type === 'weixin'
|
||||
) {
|
||||
formData.value.template.toTag = undefined;
|
||||
|
@ -813,6 +819,7 @@ const resetPublicFiles = () => {
|
|||
if (formData.value.type === 'email')
|
||||
formData.value.template.toParty = undefined;
|
||||
// formData.value.description = '';
|
||||
formData.value.variableDefinitions = [];
|
||||
};
|
||||
|
||||
// 根据通知方式展示对应的字段
|
||||
|
@ -862,7 +869,14 @@ const formRules = ref({
|
|||
// 钉钉
|
||||
'template.agentId': [{ required: true, message: '请输入agentId' }],
|
||||
'template.messageType': [{ required: true, message: '请选择消息类型' }],
|
||||
'template.markdown.title': [{ required: true, message: '请输入标题' }],
|
||||
'template.markdown.title': [
|
||||
{ required: true, message: '请输入标题' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
'template.link.title': [
|
||||
{ required: true, message: '请输入标题' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
// 'template.url': [{ required: true, message: '请输入WebHook' }],
|
||||
// 微信
|
||||
// 'template.agentId': [{ required: true, message: '请输入agentId' }],
|
||||
|
@ -876,7 +890,21 @@ const formRules = ref({
|
|||
'template.signName': [{ required: true, message: '请输入签名' }],
|
||||
// webhook
|
||||
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||
'template.message': [{ required: true, message: '请输入' }],
|
||||
'template.message': [
|
||||
{ required: true, message: '请输入' },
|
||||
{ max: 500, message: '最多可输入500个字符' },
|
||||
],
|
||||
'template.calledShowNumbers': [
|
||||
{
|
||||
trigger: 'blur',
|
||||
validator(_rule: Rule, value: string) {
|
||||
if (!phoneRegEx(value)) {
|
||||
return Promise.reject('请输入有效号码');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { resetFields, validate, validateInfos, clearValidate } = useForm(
|
||||
|
@ -884,39 +912,97 @@ const { resetFields, validate, validateInfos, clearValidate } = useForm(
|
|||
formRules.value,
|
||||
);
|
||||
|
||||
// 钉钉机器人markdown标题变量提取
|
||||
watch(
|
||||
() => formData.value.template.message,
|
||||
() => formData.value.template.markdown?.title,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
// 已经存在的变量
|
||||
const oldKey = formData.value.variableDefinitions?.map((m) => m.id);
|
||||
// 正则提取${}里面的值
|
||||
const pattern = /(?<=\$\{).*?(?=\})/g;
|
||||
const titleList = val.match(pattern)?.filter((f) => f);
|
||||
const newKey = [...new Set(titleList)];
|
||||
const result = newKey?.map((m) =>
|
||||
oldKey.includes(m)
|
||||
? formData.value.variableDefinitions.find(
|
||||
(item) => item.id === m,
|
||||
)
|
||||
: {
|
||||
id: m,
|
||||
name: '',
|
||||
type: 'string',
|
||||
format: '%s',
|
||||
},
|
||||
);
|
||||
formData.value.variableDefinitions = result as IVariableDefinitions[];
|
||||
variableReg(val);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
// 钉钉机器人link标题变量提取
|
||||
watch(
|
||||
() => formData.value.template.link?.title,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
variableReg(val);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
// 邮件标题变量提取
|
||||
watch(
|
||||
() => formData.value.template.subject,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
variableReg(val);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// const clearValid = () => {
|
||||
// setTimeout(() => {
|
||||
// formData.value.variableDefinitions = [];
|
||||
// clearValidate();
|
||||
// }, 200);
|
||||
// };
|
||||
// 模板内容变量提取
|
||||
watch(
|
||||
() => formData.value.template.message,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
variableReg(val);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
// webhook请求体变量提取
|
||||
watch(
|
||||
() => formData.value.template.body,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
variableReg(val);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
/**
|
||||
* 根据字段输入内容, 提取变量
|
||||
* @param value
|
||||
*/
|
||||
const variableReg = (value: string) => {
|
||||
// 已经存在的变量
|
||||
const oldKey = formData.value.variableDefinitions?.map((m) => m.id);
|
||||
// 正则提取${}里面的值
|
||||
const pattern = /(?<=\$\{).*?(?=\})/g;
|
||||
const titleList = value.match(pattern)?.filter((f) => f);
|
||||
const newKey = [...new Set(titleList)];
|
||||
const result = newKey?.map((m) =>
|
||||
oldKey.includes(m)
|
||||
? formData.value.variableDefinitions.find((item) => item.id === m)
|
||||
: {
|
||||
id: m,
|
||||
name: '',
|
||||
type: 'string',
|
||||
format: '%s',
|
||||
},
|
||||
);
|
||||
formData.value.variableDefinitions = [
|
||||
...new Set([
|
||||
...formData.value.variableDefinitions,
|
||||
...(result as IVariableDefinitions[]),
|
||||
]),
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* 钉钉机器人 消息类型选择改变
|
||||
*/
|
||||
const handleMessageTypeChange = () => {
|
||||
formData.value.variableDefinitions = [];
|
||||
formData.value.template.message = '';
|
||||
if (formData.value.template.link) {
|
||||
formData.value.template.link.title = '';
|
||||
formData.value.template.link.picUrl = '';
|
||||
formData.value.template.link.messageUrl = '';
|
||||
}
|
||||
if (formData.value.template.markdown) {
|
||||
formData.value.template.markdown.title = '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取详情
|
||||
|
@ -961,7 +1047,6 @@ const handleTypeChange = () => {
|
|||
const handleProviderChange = () => {
|
||||
formData.value.template =
|
||||
TEMPLATE_FIELD_MAP[formData.value.type][formData.value.provider];
|
||||
console.log('formData.value.template: ', formData.value.template);
|
||||
getConfigList();
|
||||
resetPublicFiles();
|
||||
};
|
||||
|
@ -1023,29 +1108,32 @@ const handleSubmit = () => {
|
|||
if (formData.value.template.messageType === 'link')
|
||||
delete formData.value.template.markdown;
|
||||
// console.log('formData.value: ', formData.value);
|
||||
validate()
|
||||
.then(async () => {
|
||||
formData.value.template.ttsCode =
|
||||
formData.value.template.templateCode;
|
||||
btnLoading.value = true;
|
||||
let res;
|
||||
if (!formData.value.id) {
|
||||
res = await templateApi.save(formData.value);
|
||||
} else {
|
||||
res = await templateApi.update(formData.value);
|
||||
}
|
||||
// console.log('res: ', res);
|
||||
if (res?.success) {
|
||||
message.success('保存成功');
|
||||
router.back();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('err: ', err);
|
||||
})
|
||||
.finally(() => {
|
||||
btnLoading.value = false;
|
||||
});
|
||||
// 提交必填验证无法通过, 实际已有值, 问题未知, 暂时解决方法: 延迟验证
|
||||
setTimeout(() => {
|
||||
validate()
|
||||
.then(async () => {
|
||||
formData.value.template.ttsCode =
|
||||
formData.value.template.templateCode;
|
||||
btnLoading.value = true;
|
||||
let res;
|
||||
if (!formData.value.id) {
|
||||
res = await templateApi.save(formData.value);
|
||||
} else {
|
||||
res = await templateApi.update(formData.value);
|
||||
}
|
||||
// console.log('res: ', res);
|
||||
if (res?.success) {
|
||||
message.success('保存成功');
|
||||
router.back();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('err: ', err);
|
||||
})
|
||||
.finally(() => {
|
||||
btnLoading.value = false;
|
||||
});
|
||||
}, 200);
|
||||
};
|
||||
|
||||
// test
|
||||
|
|
|
@ -114,6 +114,14 @@
|
|||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<span v-if="column.dataIndex === 'type'">
|
||||
{{ getMethodTxt(record.type) }}
|
||||
</span>
|
||||
<span v-if="column.dataIndex === 'provider'">
|
||||
{{ getProviderTxt(record.type, record.provider) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
|
@ -159,9 +167,9 @@
|
|||
<script setup lang="ts">
|
||||
import TemplateApi from '@/api/notice/template';
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { getImage, LocalStore } from '@/utils/comm';
|
||||
// import { getImage, LocalStore } from '@/utils/comm';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||
// import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||
|
||||
import { NOTICE_METHOD, MSG_TYPE } from '@/views/notice/const';
|
||||
|
||||
|
@ -254,6 +262,14 @@ const getLogo = (type: string, provider: string) => {
|
|||
const getMethodTxt = (type: string) => {
|
||||
return NOTICE_METHOD.find((f) => f.value === type)?.label;
|
||||
};
|
||||
/**
|
||||
* 根据类型展示对应文案
|
||||
* @param type
|
||||
* @param provider
|
||||
*/
|
||||
const getProviderTxt = (type: string, provider: string) => {
|
||||
return MSG_TYPE[type].find((f: any) => f.value === provider)?.label;
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增
|
||||
|
@ -301,13 +317,6 @@ const handleExport = () => {
|
|||
downloadObject(configRef.value.dataSource, `通知配置`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
const handleView = (id: string) => {
|
||||
message.warn(id + '暂未开发');
|
||||
};
|
||||
|
||||
const syncVis = ref(false);
|
||||
const debugVis = ref(false);
|
||||
const logVis = ref(false);
|
||||
|
@ -326,8 +335,6 @@ const getActions = (
|
|||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
// visible.value = true;
|
||||
// current.value = data;
|
||||
menuStory.jumpPage('notice/Template/Detail', {
|
||||
id: data.id,
|
||||
});
|
||||
|
|
|
@ -0,0 +1,434 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<div>
|
||||
<Search
|
||||
:columns="query.columns"
|
||||
target="device-instance"
|
||||
@search="handleSearch"
|
||||
></Search>
|
||||
<JTable
|
||||
:columns="columns"
|
||||
:request="queryList"
|
||||
ref="tableRef"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
}"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="add"
|
||||
><plus-outlined />新增</a-button
|
||||
>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value="slotProps"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
v-bind="slotProps"
|
||||
:status="slotProps.state?.value"
|
||||
:statusText="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
enabled: 'success',
|
||||
disabled: 'error',
|
||||
}"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img
|
||||
:src="getImage('/alarm/alarm-config.png')"
|
||||
/>
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3 style="font-weight: 600">
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="content-des-title">
|
||||
关联场景联动
|
||||
</div>
|
||||
<div class="rule-desc">
|
||||
{{ (slotProps?.scene || []).map((item: any) => item?.name).join(',') || '' }}
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div class="content-des-title">
|
||||
告警级别
|
||||
</div>
|
||||
<div class="rule-desc">
|
||||
{{ (Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title ||
|
||||
slotProps.level }}
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<a-tooltip
|
||||
v-bind="item.tooltip"
|
||||
:title="item.disabled && item.tooltip.title"
|
||||
v-if="
|
||||
item.key != 'trigger' ||
|
||||
slotProps.sceneTriggerType == 'manual'
|
||||
"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="item.popConfirm"
|
||||
v-bind="item.popConfirm"
|
||||
:disabled="item.disabled"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a-button :disabled="item.disabled">
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<template v-else>
|
||||
<a-button
|
||||
:disabled="item.disabled"
|
||||
@click="item.onClick"
|
||||
>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
<template #targetType="slotProps">
|
||||
<span>{{ map[slotProps.targetType] }}</span>
|
||||
</template>
|
||||
<template #level="slotProps">
|
||||
<a-tooltip
|
||||
placement="topLeft"
|
||||
:title="(Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title ||
|
||||
slotProps.level"
|
||||
>
|
||||
<div class="ellipsis">
|
||||
{{ (Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title ||
|
||||
slotProps.level }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #sceneId="slotProps">
|
||||
<span
|
||||
>{{(slotProps?.scene || []).map((item: any) => item?.name).join(',') || ''}}</span
|
||||
>
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
:text="
|
||||
slotProps.state?.value === 'enabled'
|
||||
? '正常'
|
||||
: '禁用'
|
||||
"
|
||||
:status="
|
||||
slotProps.state?.value === 'enabled'
|
||||
? 'success'
|
||||
: 'error'
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps)"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<span
|
||||
v-if="
|
||||
i.key != 'trigger' ||
|
||||
slotProps.sceneTriggerType == 'manual'
|
||||
"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import JTable from '@/components/Table';
|
||||
import {
|
||||
queryList,
|
||||
_enable,
|
||||
_disable,
|
||||
remove,
|
||||
_execute,
|
||||
} from '@/api/rule-engine/configuration';
|
||||
import { queryLevel } from '@/api/rule-engine/config';
|
||||
import { Store } from 'jetlinks-store';
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
const params = ref<Record<string, any>>({});
|
||||
let isAdd = ref<number>(0);
|
||||
let title = ref<string>('');
|
||||
const tableRef = ref<Record<string, any>>({});
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'targetType',
|
||||
key: 'targetType',
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '告警级别',
|
||||
dataIndex: 'level',
|
||||
key: 'level',
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '关联场景联动',
|
||||
dataIndex: 'sceneId',
|
||||
wdith: 250,
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
const map = {
|
||||
product: '产品',
|
||||
device: '设备',
|
||||
org: '组织',
|
||||
other: '其他',
|
||||
};
|
||||
const query = {
|
||||
columns: [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: '正常',
|
||||
value: 'enabled',
|
||||
},
|
||||
{
|
||||
label: '禁用',
|
||||
value: 'disabled',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
key: 'description',
|
||||
dataIndex: 'description',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const handleSearch = (e: any) => {
|
||||
params.value = e;
|
||||
};
|
||||
const queryDefaultLevel = () => {
|
||||
queryLevel().then((res) => {
|
||||
if (res.status === 200) {
|
||||
Store.set('default-level', res.result?.levels || []);
|
||||
}
|
||||
});
|
||||
};
|
||||
queryDefaultLevel();
|
||||
const getActions = (
|
||||
data: Partial<Record<string, any>>,
|
||||
type?: 'card' | 'table',
|
||||
): ActionsType[] => {
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
const actions = [
|
||||
{
|
||||
key: 'trigger',
|
||||
text: '手动触发',
|
||||
disabled: data?.state?.value === 'disabled',
|
||||
tooltip: {
|
||||
title:
|
||||
data?.state?.value === 'disabled'
|
||||
? '未启用,不能手动触发'
|
||||
: '手动触发',
|
||||
},
|
||||
popConfirm: {
|
||||
title: '确定手动触发?',
|
||||
onConfirm: async () => {
|
||||
const scene = (data.scene || [])
|
||||
.filter((item: any) => item?.triggerType === 'manual')
|
||||
.map((i) => {
|
||||
return { id: i?.id };
|
||||
});
|
||||
_execute(scene).then((res) => {
|
||||
if (res.status === 200) {
|
||||
message.success('操作成功');
|
||||
tableRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败');
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
icon: 'LikeOutlined',
|
||||
},
|
||||
{
|
||||
key: 'edit',
|
||||
text: '编辑',
|
||||
tooltip: {
|
||||
title: '编辑',
|
||||
},
|
||||
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
title.value = '编辑';
|
||||
isAdd.value = 2;
|
||||
nextTick(() => {});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
text: data.state?.value !== 'disabled' ? '禁用' : '启用',
|
||||
tooltip: {
|
||||
title: data.state?.value !== 'disabled' ? '禁用' : '启用',
|
||||
},
|
||||
icon:
|
||||
data.state?.value !== 'disabled'
|
||||
? 'StopOutlined'
|
||||
: 'CheckCircleOutlined',
|
||||
popConfirm: {
|
||||
title: `${
|
||||
data.state?.value !== 'disabled'
|
||||
? '禁用告警不会影响关联的场景状态,确定要禁用吗'
|
||||
: '确认启用'
|
||||
}?`,
|
||||
onConfirm: async () => {
|
||||
let response = undefined;
|
||||
if (data.state?.value === 'disabled') {
|
||||
response = await _enable(data.id);
|
||||
} else {
|
||||
response = await _disable(data.id);
|
||||
}
|
||||
if (response && response.status === 200) {
|
||||
message.success('操作成功!');
|
||||
tableRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
text: '删除',
|
||||
disabled: data?.state?.value !== 'disabled',
|
||||
tooltip: {
|
||||
title:
|
||||
data?.state?.value !== 'disabled'
|
||||
? '请先禁用该告警,再删除'
|
||||
: '删除',
|
||||
},
|
||||
popConfirm: {
|
||||
title: '确认删除?',
|
||||
onConfirm: async () => {
|
||||
const resp = await remove(data.id);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
tableRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
},
|
||||
},
|
||||
icon: 'DeleteOutlined',
|
||||
},
|
||||
];
|
||||
if (type === 'card')
|
||||
return actions.filter((i: ActionsType) => i.key !== 'view');
|
||||
return actions;
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.content-des-title {
|
||||
font-size: 12px;
|
||||
}
|
||||
.rule-desc {
|
||||
white-space: nowrap; /*强制在同一行内显示所有文本,直到文本结束或者遭遇br标签对象才换行。*/
|
||||
overflow: hidden; /*超出部分隐藏*/
|
||||
text-overflow: ellipsis; /*隐藏部分以省略号代替*/
|
||||
}
|
||||
</style>
|
|
@ -193,6 +193,14 @@ const query = {
|
|||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
key: 'description',
|
||||
dataIndex: 'description',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const columns = [
|
||||
|
@ -320,7 +328,6 @@ const refresh = () => {
|
|||
tableRef.value?.reload();
|
||||
};
|
||||
const handleSearch = (e: any) => {
|
||||
console.log(e);
|
||||
params.value = e;
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
<template>
|
||||
<a-modal
|
||||
title='触发规则'
|
||||
visible
|
||||
:width='820'
|
||||
@click='save'
|
||||
@cancel='cancel'
|
||||
>
|
||||
<a-steps :current='addModel.stepNumber'>
|
||||
<a-step>
|
||||
<template #title>选择产品</template>
|
||||
</a-step>
|
||||
<a-step>
|
||||
<template #title>选择设备</template>
|
||||
</a-step>
|
||||
<a-step>
|
||||
<template #title>触发类型</template>
|
||||
</a-step>
|
||||
</a-steps>
|
||||
<div class='steps-content'>
|
||||
<Product :rowKey='addModel.productId' />
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class='steps-action'>
|
||||
<template>
|
||||
<a-button v-if='addModel.stepNumber === 0' @click='cancel'>取消</a-button>
|
||||
<a-button v-else>上一步</a-button>
|
||||
</template>
|
||||
<template>
|
||||
<a-button type='primary' v-if='addModel.stepNumber < 2'>下一步</a-button>
|
||||
<a-button type='primary' v-else>确定</a-button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='AddModel'>
|
||||
import type { PropType } from 'vue'
|
||||
import { TriggerDevice } from '@/views/rule-engine/Scene/typings'
|
||||
import { onlyMessage } from '@/utils/comm'
|
||||
import { detail as deviceDetail } from '@/api/device/instance'
|
||||
import Product from './Product.vue'
|
||||
|
||||
type Emit = {
|
||||
(e: 'cancel'): void
|
||||
(e: 'update:value', data: TriggerDevice): void
|
||||
(e: 'update:options', data: any): void
|
||||
}
|
||||
|
||||
interface AddModelType extends Omit<TriggerDevice, 'selectorValues'> {
|
||||
stepNumber: number
|
||||
deviceKeys: Array<{ label: string, value: string }>
|
||||
orgId: Array<{ label: string, value: string }>
|
||||
productDetail: any
|
||||
selectorValues: Array<{ label: string, value: string }>
|
||||
metadata: {
|
||||
properties?: any[]
|
||||
functions?: any[]
|
||||
events?: any[]
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Object as PropType<TriggerDevice>,
|
||||
default: () => ({
|
||||
productId: '',
|
||||
selector: 'fixed',
|
||||
selectorValues: [],
|
||||
})
|
||||
},
|
||||
options: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const addModel = reactive<AddModelType>({
|
||||
productId: '',
|
||||
selector: 'fixed',
|
||||
selectorValues: [],
|
||||
stepNumber: 0,
|
||||
deviceKeys: [],
|
||||
orgId: [],
|
||||
productDetail: {},
|
||||
metadata: {}
|
||||
})
|
||||
|
||||
Object.assign(addModel, props.value)
|
||||
|
||||
const handleOptions = () => {
|
||||
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
emit("cancel")
|
||||
}
|
||||
|
||||
const handleMetadata = (metadata: string) => {
|
||||
try {
|
||||
addModel.metadata = JSON.parse(metadata)
|
||||
} catch (e) {
|
||||
console.warn('handleMetadata: ' + e)
|
||||
}
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
if (addModel.stepNumber === 0) {
|
||||
addModel.productId ? addModel.stepNumber = 1 : onlyMessage('请选择产品', 'error')
|
||||
} else if (addModel.stepNumber === 1) {
|
||||
const isFixed = addModel.selector === 'fixed' // 是否选择方式为设备
|
||||
if ((['fixed', 'org'].includes(addModel.selector) ) && addModel.selectorValues?.length) {
|
||||
return onlyMessage(isFixed ? '请选择设备' : '请选择部门', 'error')
|
||||
}
|
||||
// 选择方式为设备且仅选中一个设备时,物模型取该设备
|
||||
if (isFixed && addModel.selectorValues?.length === 1) {
|
||||
const resp = await deviceDetail(addModel.selectorValues[0].value)
|
||||
addModel.metadata
|
||||
} else {
|
||||
|
||||
}
|
||||
//
|
||||
}
|
||||
// handleOptions()
|
||||
// emit('update:value', {})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,234 @@
|
|||
<template>
|
||||
<Search
|
||||
:columns="columns"
|
||||
type='simple'
|
||||
@search="handleSearch"
|
||||
class='search'
|
||||
/>
|
||||
<j-table
|
||||
:columns='columns'
|
||||
ref='actionRef'
|
||||
:request='productQuery'
|
||||
:gridColumn='2'
|
||||
model='CARD'
|
||||
>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value='slotProps'
|
||||
:active="selectedRowKeys.includes(slotProps.id)"
|
||||
:status="slotProps.state"
|
||||
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
||||
:statusNames="{ 1: 'success', 0: 'error', }"
|
||||
@click="handleClick"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img :src="getImage('/device-product.png')" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3 style="font-weight: 600" >
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
</div>
|
||||
<div>直连设备</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
</j-table>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='Product'>
|
||||
import { getProviders, queryGatewayList, queryProductList } from '@/api/device/product'
|
||||
import { queryTree } from '@/api/device/category'
|
||||
import { getTreeData_api } from '@/api/system/department'
|
||||
import { isNoCommunity } from '@/utils/utils'
|
||||
import { getImage } from '@/utils/comm'
|
||||
|
||||
const actionRef = ref()
|
||||
const params = ref({})
|
||||
const props = defineProps({
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const selectedRowKeys = ref(props.rowKey)
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
width: 300,
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '网关类型',
|
||||
dataIndex: 'accessProvider',
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () => getProviders().then((resp: any) => {
|
||||
if (isNoCommunity) {
|
||||
return (resp?.result || []).map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id
|
||||
}))
|
||||
} else {
|
||||
return (resp?.result || []).filter((item: any) => [
|
||||
'mqtt-server-gateway',
|
||||
'http-server-gateway',
|
||||
'mqtt-client-gateway',
|
||||
'tcp-server-gateway',
|
||||
].includes(item.id))
|
||||
.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '接入方式',
|
||||
dataIndex: 'accessName',
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () => queryGatewayList().then((resp: any) =>
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name, value: item.id
|
||||
}))
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '设备类型',
|
||||
dataIndex: 'deviceType',
|
||||
width: 150,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '直连设备', value: 'device' },
|
||||
{ label: '网关子设备', value: 'childrenDevice' },
|
||||
{ label: '网关设备', value: 'gateway' },
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
width: '90px',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '禁用', value: 0 },
|
||||
{ label: '正常', value: 1 },
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'describe',
|
||||
ellipsis: true,
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
dataIndex: 'classifiedId',
|
||||
title: '分类',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'treeSelect',
|
||||
options: queryTree({ paging: false }).then(resp => resp.result),
|
||||
componentProps: {
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
dataIndex: 'id$dim-assets',
|
||||
title: '所属组织',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'treeSelect',
|
||||
options: getTreeData_api({ paging: false }).then((resp: any) => {
|
||||
const formatValue = (list: any[]) => {
|
||||
return list.map((item: any) => {
|
||||
if (item.children) {
|
||||
item.children = formatValue(item.children);
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
value: JSON.stringify({
|
||||
assetType: 'product',
|
||||
targets: [
|
||||
{
|
||||
type: 'org',
|
||||
id: item.id,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
return formatValue(resp.result)
|
||||
}),
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const handleSearch = (p: any) => {
|
||||
params.value = p
|
||||
actionRef.value.required()
|
||||
}
|
||||
|
||||
const productQuery = (p: any) => {
|
||||
const sorts: any = [];
|
||||
|
||||
if (props.rowKey) {
|
||||
sorts.push({
|
||||
name: 'id',
|
||||
value: props.rowKey,
|
||||
});
|
||||
}
|
||||
sorts.push({ name: 'createTime', order: 'desc' });
|
||||
p.sorts = sorts
|
||||
return queryProductList(p)
|
||||
}
|
||||
|
||||
const handleClick = (detail: any) => {
|
||||
const _selected = new Set(selectedRowKeys.value)
|
||||
if (_selected.has(detail.id)) {
|
||||
_selected.delete(detail.id)
|
||||
} else {
|
||||
_selected.add(detail.id)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
.search {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<div class='device'>
|
||||
<a-form-item
|
||||
:rules='rules'
|
||||
name='device'
|
||||
>
|
||||
<template #label>
|
||||
<TitleComponent data='触发规则' style='font-size: 14px;' />
|
||||
</template>
|
||||
<AddButton
|
||||
style='width: 100%'
|
||||
@click='visible = true'
|
||||
>
|
||||
<Title :options='data.options.trigger' />
|
||||
</AddButton>
|
||||
</a-form-item>
|
||||
<AddModel v-if='visible' @cancel='visible = false' v-model='data.device' v-model:options='data.options.trigger' />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='SceneSaveDevice'>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useSceneStore } from '@/store/scene'
|
||||
import AddModel from './AddModal.vue'
|
||||
import AddButton from '../components/AddButton.vue'
|
||||
import Title from '../components/Title.vue'
|
||||
|
||||
const sceneStore = useSceneStore()
|
||||
const { data } = storeToRefs(sceneStore)
|
||||
const visible = ref(false)
|
||||
|
||||
const rules = [{
|
||||
validator(_: any, v: any) {
|
||||
if (!v) {
|
||||
return Promise.reject(new Error('请配置设备触发规则'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
}]
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'index'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'inex'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<div class='rule-button-warp' :style='style'>
|
||||
<div class='rule-button add-button' @click='click'>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='AddButton'>
|
||||
import type { PropType, CSSProperties} from 'vue'
|
||||
|
||||
type Emit = {
|
||||
(e: 'click'): void
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
style: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const click = () => {
|
||||
emit('click')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
.rule-button-warp {
|
||||
display: inline-block;
|
||||
padding: 14px 16px;
|
||||
background-color: #fafafa;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
|
||||
.rule-button {
|
||||
display: inline-block;
|
||||
padding: 6px 20px;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 22px;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
color: #bdbdbd;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
border-color: #d0d0d0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<div :class='["trigger-options-content", isAdd ? "is-add" : ""]'>
|
||||
<span v-if='!isAdd'> 点击配置设备触发 </span>
|
||||
<template v-else>
|
||||
<div class='center-item'>
|
||||
<AIcon v-if='options.selectorIcon' :type='options.selectorIcon' class='icon-padding-right' />
|
||||
<span class='trigger-options-name'>
|
||||
<Ellipsis style='width: 310px'>
|
||||
{{ options.name }}
|
||||
</Ellipsis>
|
||||
</span>
|
||||
<span v-if='options.extraName'>{{ options.extraName }}</span>
|
||||
</div>
|
||||
<template v-if='options.onlyName'>
|
||||
<div v-if='options.productName' class='center-item'>
|
||||
<AIcon type='icon-chanpin1' class='icon-padding-right' />
|
||||
<span className='trigger-options-type'>{{ options.productName }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if='options.when'>
|
||||
<span className='trigger-options-when'>{{ options.when }}</span>
|
||||
</div>
|
||||
<div v-if='options.time'>
|
||||
<span className='trigger-options-time'>{{ options.time }}</span>
|
||||
</div>
|
||||
<div v-if='options.extraTime'>
|
||||
<span className='trigger-options-extraTime'>{{ options.extraTime }}</span>
|
||||
</div>
|
||||
<div v-if='options.action' class='center-item'>
|
||||
<AIcon :type='options.typeIcon' class='icon-padding-right' />
|
||||
<span className='trigger-options-action'>{{ options.productName }}</span>
|
||||
</div>
|
||||
<div v-if='options.type' class='center-item'>
|
||||
<AIcon :type='options.typeIcon' class='icon-padding-right' />
|
||||
<span className='trigger-options-type'>{{ options.type }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='DeviceTitle'>
|
||||
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const isAdd = computed(() => {
|
||||
console.log(props.options, Object.keys(props.options).length)
|
||||
return !!Object.keys(props.options).length
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
.trigger-options-content {
|
||||
|
||||
.center-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-padding-right {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
|
@ -2,14 +2,18 @@
|
|||
<page-container>
|
||||
<div class='scene-warp'>
|
||||
<div class='header'>
|
||||
|
||||
<Ellipsis :tooltip='data.name' style='max-width: 50%'>
|
||||
<span class='title'>{{ data.name }}</span>
|
||||
</Ellipsis>
|
||||
<div class='type'>
|
||||
<img :src='TriggerHeaderIcon[data.triggerType]' />
|
||||
{{ keyByLabel[data.triggerType] }}
|
||||
</div>
|
||||
</div>
|
||||
<a-form ref='sceneForm' :model='data'>
|
||||
|
||||
<a-form ref='sceneForm' :model='data' :colon='false' layout='vertical'>
|
||||
<Device v-if='data.triggerType === "device"' />
|
||||
<Manual v-else-if='data.triggerType === "manual"' />
|
||||
<Timer v-else-if='data.triggerType === "timer"' />
|
||||
</a-form>
|
||||
<PermissionButton
|
||||
type='primary'
|
||||
|
@ -17,19 +21,26 @@
|
|||
>
|
||||
保存
|
||||
</PermissionButton>
|
||||
<!-- <a-button type='primary' :loading='loading'>保存</a-button>-->
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='Scene'>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useSceneStore } from '@/store/scene'
|
||||
import { TriggerHeaderIcon } from './asstes'
|
||||
import { keyByLabel } from '../typings'
|
||||
import Device from './Device/index.vue'
|
||||
import Manual from './Manual/index.vue'
|
||||
import Timer from './Timer/index.vue'
|
||||
|
||||
const sceneStore = useSceneStore()
|
||||
const { data } = storeToRefs(sceneStore)
|
||||
const { getDetail } = sceneStore
|
||||
|
||||
const { getDetail, data } = useSceneStore()
|
||||
const route = useRoute();
|
||||
const loading = ref(false)
|
||||
console.log('data',data)
|
||||
|
||||
getDetail(route.query.id as string)
|
||||
|
||||
|
@ -45,17 +56,24 @@ getDetail(route.query.id as string)
|
|||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.title {
|
||||
font-size: 20px;
|
||||
color: rgba(#000, .8);
|
||||
font-weight: bold;
|
||||
}
|
||||
.type {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 100px;
|
||||
min-width: 80px;
|
||||
margin-left: 16px;
|
||||
padding: 4px 8px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
font-size: 14px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 2px;
|
||||
img {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,8 +85,8 @@ export default defineConfig(({ mode}) => {
|
|||
// target: 'http://192.168.33.22:8800',
|
||||
// target: 'http://192.168.32.244:8881',
|
||||
// target: 'http://47.112.135.104:5096', // opcua
|
||||
// target: 'http://120.77.179.54:8844', // 120测试
|
||||
target: 'http://47.108.63.174:8845', // 测试
|
||||
target: 'http://120.77.179.54:8844', // 120测试
|
||||
// target: 'http://47.108.63.174:8845', // 测试
|
||||
// target: 'http://120.77.179.54:8844',
|
||||
ws: 'ws://120.77.179.54:8844',
|
||||
changeOrigin: true,
|
||||
|
|
Loading…
Reference in New Issue