Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
bba6ef3c30
|
@ -37,6 +37,7 @@
|
|||
"unplugin-vue-components": "^0.22.12",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vue": "^3.2.45",
|
||||
"vue-json-viewer": "^3.0.4",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue3-markdown-it": "^1.0.10",
|
||||
"vue3-ts-jsoneditor": "^2.7.1"
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
|
@ -51,3 +51,9 @@ export const queryDevice = () =>
|
|||
|
||||
export const validateVersion = (productId: string, versionOrder: number) =>
|
||||
server.get(`/firmware/${productId}/${versionOrder}/exists`);
|
||||
|
||||
export const queryDetailList = (data: Record<string, unknown>) =>
|
||||
server.post(`/device-instance/detail/_query`, data);
|
||||
|
||||
export const queryDetailListNoPaging = (data: Record<string, unknown>) =>
|
||||
server.post(`/device-instance/detail/_query/no-paging`, data);
|
||||
|
|
|
@ -458,4 +458,28 @@ export const treeEdgeMap = (deviceId: string, data?: any) => server.post(`/edge/
|
|||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const saveEdgeMap = (deviceId: string, data?: any) => server.post(`/edge/operations/${deviceId}/device-collector-save/invoke`, data)
|
||||
export const saveEdgeMap = (deviceId: string, data?: any) => server.post(`/edge/operations/${deviceId}/device-collector-save/invoke`, data)
|
||||
|
||||
/**
|
||||
* 查询属性详情
|
||||
* @param deviceId
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
export const getPropertyData = (deviceId: string, params: Record<string, unknown>) => server.get(`/device-instance/${deviceId}/properties/_query`, params)
|
||||
|
||||
/**
|
||||
* 聚合查询设备属性
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const getPropertiesInfo = (deviceId: string, data: Record<string, unknown>) => server.post(`/device-instance/${deviceId}/agg/_query`, data)
|
||||
|
||||
/**
|
||||
* 聚合查询设备属性
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const getPropertiesList = (deviceId: string, property: string, data: Record<string, unknown>) => server.post(`/device-instance/${deviceId}/property/${property}/_query`, data)
|
|
@ -175,3 +175,13 @@ export const saveDevice = (data:any) => server.post('/device-product',data)
|
|||
* 更新选择设备(设备接入)
|
||||
*/
|
||||
export const updateDevice = (data:any) => server.patch('/device-product',data)
|
||||
|
||||
/**
|
||||
* 获取操作符
|
||||
*/
|
||||
export const getOperator = () => server.get<OperatorItem>('/property-calculate-rule/description')
|
||||
|
||||
/**
|
||||
* 获取聚合函数列表
|
||||
*/
|
||||
export const getStreamingAggType = () => server.get<Record<string, string>[]>('/dictionary/streaming-agg-type/items')
|
|
@ -16,21 +16,21 @@ export default {
|
|||
debug: (data: any, configId: string, templateId: string) => post(`/notifier/${configId}/${templateId}/_send`, data),
|
||||
getHistory: (data: any, id: string) => post(`/notify/history/config/${id}/_query`, data),
|
||||
// 获取所有平台用户
|
||||
getPlatformUsers: () => post<any>(`/user/_query/no-paging`, { paging: false }),
|
||||
getPlatformUsers: (data: any) => post<any>(`/user/_query/no-paging`, data),
|
||||
// 钉钉部门
|
||||
dingTalkDept: (id: string) => get<any>(`/notifier/dingtalk/corp/${id}/departments/tree`),
|
||||
// 钉钉部门人员
|
||||
getDingTalkUsers: (configId: string, deptId: string) => get(`/notifier/dingtalk/corp/${configId}/${deptId}/users`),
|
||||
getDingTalkUsers: (configId: string, deptId: string) => get<any>(`/notifier/dingtalk/corp/${configId}/${deptId}/users`),
|
||||
// 钉钉已经绑定的人员
|
||||
getDingTalkBindUsers: (id: string) => get(`/user/third-party/dingTalk_dingTalkMessage/${id}`),
|
||||
getDingTalkBindUsers: (id: string) => get<any>(`/user/third-party/dingTalk_dingTalkMessage/${id}`),
|
||||
// 钉钉绑定用户
|
||||
dingTalkBindUser: (data: any, id: string) => patch(`/user/third-party/dingTalk_dingTalkMessage/${id}`, data),
|
||||
dingTalkBindUser: (data: { userId: string; providerName: string; thirdPartyUserId: string }[], id: string) => patch(`/user/third-party/dingTalk_dingTalkMessage/${id}`, data),
|
||||
// 微信部门
|
||||
weChatDept: (id: string) => get<any>(`/notifier/wechat/corp/${id}/departments`),
|
||||
// 微信部门人员
|
||||
getWeChatUsers: (configId: string, deptId: string) => get(`/notifier/wechat/corp/${configId}/${deptId}/users`),
|
||||
getWeChatUsers: (configId: string, deptId: string) => get<any>(`/notifier/wechat/corp/${configId}/${deptId}/users`),
|
||||
// 微信已经绑定的人员
|
||||
getWeChatBindUsers: (id: string) => get(`/user/third-party/weixin_corpMessage/${id}`),
|
||||
getWeChatBindUsers: (id: string) => get<any>(`/user/third-party/weixin_corpMessage/${id}`),
|
||||
// 微信绑定用户
|
||||
weChatBindUser: (data: any, id: string) => patch(`/user/third-party/weixin_corpMessage/${id}`, data),
|
||||
// 解绑
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
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);
|
||||
/**
|
||||
* 下拉框场景数据
|
||||
*/
|
||||
export const getScene = (params:Record<string,any>) => server.get('/scene/_query/no-paging?paging=false',params);
|
||||
/**
|
||||
* 获取配置类型
|
||||
*/
|
||||
export const getTargetTypes = () => server.get('/alarm/config/target-type/supports');
|
||||
/**
|
||||
* 保存基本设置
|
||||
*/
|
||||
export const save = (data:any) =>server.post('/alarm/config',data);
|
||||
/**
|
||||
* 获取基础设置数据
|
||||
*/
|
||||
export const detail = (id:string) => server.get(`/alarm/config/${id}`);
|
|
@ -5,4 +5,6 @@ export const modify = (id: string, data: any) => server.put(`/scene/${id}`, data
|
|||
|
||||
export const save = (data: any) => server.post(`/scene`, data)
|
||||
|
||||
export const detail = (id: string) => server.get(`/scene/${id}`)
|
||||
export const detail = (id: string) => server.get(`/scene/${id}`)
|
||||
|
||||
export const query = (data: any) => server.post('/scene/_query/',data);
|
|
@ -0,0 +1,14 @@
|
|||
import server from '@/utils/request';
|
||||
|
||||
|
||||
// 获取应用管理列表
|
||||
export const getApplyList_api = (data: any) => server.post(`/application/_query/`, data)
|
||||
// 修改应用状态
|
||||
export const changeApplyStatus_api = (id:string,data: any) => server.put(`/application/${id}`, data)
|
||||
// 删除应用
|
||||
export const delApply_api = (id:string) => server.remove(`/application/${id}`)
|
||||
|
||||
|
||||
|
||||
// 获取组织列表
|
||||
export const getDepartmentList_api = () => server.get(`/organization/_all/tree`);
|
|
@ -52,7 +52,10 @@ const iconKeys = [
|
|||
'RightOutlined',
|
||||
'FileTextOutlined',
|
||||
'UploadOutlined',
|
||||
'ArrowLeftOutlined'
|
||||
'LikeOutlined',
|
||||
'ArrowLeftOutlined',
|
||||
'DownloadOutlined',
|
||||
'PauseOutlined'
|
||||
]
|
||||
|
||||
const Icon = (props: {type: string}) => {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<div class="advance-box">
|
||||
<div class="left">
|
||||
<Editor
|
||||
ref="editor"
|
||||
mode="advance"
|
||||
key="advance"
|
||||
v-model:value="_value"
|
||||
|
@ -16,7 +17,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="right">
|
||||
<Operator :id="id" />
|
||||
<Operator :id="id" @add-operator-value="addOperatorValue"/>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
|
@ -44,10 +45,15 @@ const handleCancel = () => {
|
|||
emit('change', 'simple')
|
||||
}
|
||||
const handleOk = () => {
|
||||
console.log(_value.value)
|
||||
emit('update:value', _value.value)
|
||||
emit('change', 'simple')
|
||||
}
|
||||
|
||||
const editor = ref()
|
||||
const addOperatorValue = (val: string) => {
|
||||
editor.value.addOperatorValue(val)
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="top">
|
||||
<div class="left">
|
||||
<span v-for="item in symbolList.filter((t: SymbolType, i: number) => i <= 3)" :key="item.key"
|
||||
@click="handleInsertCode(item.value)">
|
||||
@click="addOperatorValue(item.value)">
|
||||
{{ item.value }}
|
||||
</span>
|
||||
<span>
|
||||
|
@ -12,7 +12,7 @@
|
|||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item v-for="item in symbolList.filter((t: SymbolType, i: number) => i > 6)" :key="item.key"
|
||||
@click="handleInsertCode(item.value)">
|
||||
@click="addOperatorValue(item.value)">
|
||||
{{ item.value }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
|
@ -149,7 +149,7 @@ onMounted(() => {
|
|||
}, 100);
|
||||
})
|
||||
|
||||
const handleInsertCode = (val: string) => {
|
||||
const addOperatorValue = (val: string) => {
|
||||
editor.value?.insert(val)
|
||||
}
|
||||
|
||||
|
@ -159,6 +159,10 @@ const fullscreenClick = () => {
|
|||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
addOperatorValue
|
||||
})
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.editor-box {
|
||||
|
|
|
@ -1,51 +1,64 @@
|
|||
<template>
|
||||
<div class="operator-box">
|
||||
<a-input-search @search="search" allow-clear placeholder="搜索关键字" />
|
||||
<a-tree class="tree" @select="selectTree" :field-names="{ title: 'name', key: 'id', }" auto-expand-parent
|
||||
:tree-data="data">
|
||||
<template #title="node">
|
||||
<div class="node">
|
||||
<div>{{ node.name }}</div>
|
||||
<div :class="node.children?.length > 0 ? 'parent' : 'add'">
|
||||
<a-popover v-if="node.type === 'property'" placement="right" title="请选择使用值" @visibleChange="setVisible">
|
||||
<template #content>
|
||||
<a-space direction="vertical">
|
||||
<a-tooltip placement="right" title="实时值为空时获取上一有效值补齐,实时值不为空则使用实时值">
|
||||
<a-button type="text" @click="recentClick(node)">
|
||||
$recent实时值
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="right" title="实时值的上一有效值">
|
||||
<a-button @click="lastClick(node)" type="text">
|
||||
上一值
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
<a @click="setVisible(true)">添加</a>
|
||||
</a-popover>
|
||||
<div class="tree">
|
||||
<a-tree @select="selectTree" :field-names="{ title: 'name', key: 'id', }" auto-expand-parent
|
||||
:tree-data="data">
|
||||
<template #title="node">
|
||||
<div class="node">
|
||||
<div>{{ node.name }}</div>
|
||||
<div :class="node.children?.length > 0 ? 'parent' : 'add'">
|
||||
<a-popover v-if="node.type === 'property'" placement="right" title="请选择使用值">
|
||||
<template #content>
|
||||
<a-space direction="vertical">
|
||||
<a-tooltip placement="right" title="实时值为空时获取上一有效值补齐,实时值不为空则使用实时值">
|
||||
<a-button type="text" @click="recentClick(node)">
|
||||
$recent实时值
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="right" title="实时值的上一有效值">
|
||||
<a-button @click="lastClick(node)" type="text">
|
||||
上一值
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
<a>添加</a>
|
||||
</a-popover>
|
||||
|
||||
<a v-else @click="addClick(node)">
|
||||
添加
|
||||
</a>
|
||||
<a v-else @click="addClick(node)">
|
||||
添加
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-tree>
|
||||
</template>
|
||||
</a-tree>
|
||||
</div>
|
||||
<div class="explain">
|
||||
<ReactMarkdown>{{ item?.description || '' }}</ReactMarkdown>
|
||||
<Markdown :source="item?.description || ''"></Markdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="Operator">
|
||||
import { useProductStore } from '@/store/product';
|
||||
import type { OperatorItem } from './typings';
|
||||
import { treeFilter } from '@/utils/tree'
|
||||
import { Store } from 'jetlinks-store';
|
||||
import { PropertyMetadata } from '@/views/device/Product/typings';
|
||||
import { getOperator } from '@/api/device/product'
|
||||
import Markdown from 'vue3-markdown-it'
|
||||
|
||||
const props = defineProps({
|
||||
id: String
|
||||
})
|
||||
|
||||
interface Emits {
|
||||
(e: 'addOperatorValue', data: string): void;
|
||||
}
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const item = ref<Partial<OperatorItem>>()
|
||||
const data = ref<OperatorItem[]>([])
|
||||
const dataRef = ref<OperatorItem[]>([])
|
||||
const visible = ref(false)
|
||||
|
||||
const search = (value: string) => {
|
||||
if (value) {
|
||||
|
@ -60,22 +73,52 @@ const selectTree = (k: any, info: any) => {
|
|||
item.value = info.node as unknown as OperatorItem;
|
||||
}
|
||||
|
||||
const setVisible = (_visible: boolean) => {
|
||||
visible.value = !!visible
|
||||
}
|
||||
|
||||
const recentClick = (node: OperatorItem) => {
|
||||
Store.set('add-operator-value', `$recent("${node.id}")`);
|
||||
setVisible(!visible.value);
|
||||
emit('addOperatorValue', `$recent("${node.id}")`)
|
||||
}
|
||||
const lastClick = (node: OperatorItem) => {
|
||||
Store.set('add-operator-value', `$lastState("${node.id}")`);
|
||||
setVisible(!visible.value);
|
||||
emit('addOperatorValue', `$lastState("${node.id}")`)
|
||||
}
|
||||
const addClick = (node: OperatorItem) => {
|
||||
Store.set('add-operator-value', node.code);
|
||||
setVisible(true);
|
||||
emit('addOperatorValue', node.code)
|
||||
}
|
||||
|
||||
const productStore = useProductStore()
|
||||
|
||||
const getData = async (id?: string) => {
|
||||
const metadata = productStore.current.metadata || '{}';
|
||||
console.log('metadata', metadata)
|
||||
const _properties = JSON.parse(metadata).properties || [] as PropertyMetadata[]
|
||||
const properties = {
|
||||
id: 'property',
|
||||
name: '属性',
|
||||
description: '',
|
||||
code: '',
|
||||
children: _properties
|
||||
.filter((p: PropertyMetadata) => p.id !== id)
|
||||
.map((p: PropertyMetadata) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
description: `### ${p.name}
|
||||
\n 数据类型: ${p.valueType?.type}
|
||||
\n 是否只读: ${p.expands?.readOnly || 'false'}
|
||||
\n 可写数值范围: `,
|
||||
type: 'property',
|
||||
})),
|
||||
};
|
||||
const response = await getOperator();
|
||||
if (response.status === 200) {
|
||||
data.value = [properties, ...response.result];
|
||||
dataRef.value = [properties, ...response.result];
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.id,
|
||||
(val) => {
|
||||
getData(val)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.border {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<Editor key="simple" @change="change" v-model:value="_value" :id="id" />
|
||||
{{ ruleEditorStore.state.model }}
|
||||
<Advance v-if="ruleEditorStore.state.model === 'advance'" :model="ruleEditorStore.state.model"
|
||||
{{ _value }}
|
||||
<Advance v-if="ruleEditorStore.state.model === 'advance'" v-model:value="_value" :model="ruleEditorStore.state.model"
|
||||
:virtualRule="virtualRule" :id="id" @change="change" />
|
||||
</template>
|
||||
<script setup lang="ts" name="FRuleEditor">
|
||||
|
|
|
@ -87,7 +87,7 @@ const handleEdit = (index: number) => {
|
|||
}
|
||||
const handleDelete = (index: number) => {
|
||||
editIndex.value = -1
|
||||
_value.value.slice(index, 1)
|
||||
_value.value.splice(index, 1)
|
||||
}
|
||||
const handleClose = () => {
|
||||
editIndex.value = -1
|
||||
|
|
|
@ -1,11 +1,40 @@
|
|||
<template>
|
||||
<a-form-item :name="name.concat(['script'])">
|
||||
<f-rule-editor v-model:value="value.script" :id="id" ></f-rule-editor>
|
||||
<f-rule-editor v-model:value="value.script" :id="id"></f-rule-editor>
|
||||
</a-form-item>
|
||||
<template v-if="showWindow">
|
||||
<a-form-item label="规则配置" :name="name.concat(['isVirtualRule'])">
|
||||
<a-switch v-model:checked="value.isVirtualRule" :checked-value="true" :un-checked-value="false"
|
||||
@change="changeWindow"></a-switch>
|
||||
</a-form-item>
|
||||
<template v-if="value.isVirtualRule">
|
||||
<a-form-item label="窗口" :name="name.concat(['windowType'])" :rules="[
|
||||
{ required: true, message: '请选择窗口' },
|
||||
]">
|
||||
<a-select v-model:value="value.windowType" :options="windowTypeEnum" size="small" allow-clear></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="聚合函数" :name="name.concat(['aggType'])" :rules="[
|
||||
{ required: true, message: '请选择聚合函数' },
|
||||
]">
|
||||
<a-select v-model:value="value.aggType" :options="aggTypeOptions" size="small" allow-clear></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="spanLabel" :name="name.concat(['window', 'span'])" :rules="[
|
||||
{ required: true, message: '请输入窗口长度' },
|
||||
]">
|
||||
<a-input-number v-model:value="value.window.span" size="small" style="width: 100%;"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item :label="everyLabel" :name="name.concat(['window', 'every'])" :rules="[
|
||||
{ required: true, message: '请输入步长' },
|
||||
]">
|
||||
<a-input-number v-model:value="value.window.every" size="small" style="width: 100%;"></a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<script setup lang="ts" name="VirtualRuleParam">
|
||||
import { PropType } from 'vue';
|
||||
import FRuleEditor from '@/components/FRuleEditor/index.vue'
|
||||
import { getStreamingAggType } from '@/api/device/product'
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
|
@ -18,7 +47,11 @@ const props = defineProps({
|
|||
type: Array as PropType<string[]>,
|
||||
default: () => ([])
|
||||
},
|
||||
id: String
|
||||
id: String,
|
||||
showWindow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
interface Emits {
|
||||
|
@ -32,5 +65,54 @@ onMounted(() => {
|
|||
type: 'script'
|
||||
})
|
||||
})
|
||||
|
||||
const aggTypeOptions = ref()
|
||||
const getAggTypeList = async () => {
|
||||
aggTypeOptions.value = await getStreamingAggType().then((resp) =>
|
||||
resp.result.map((item: any) => ({
|
||||
label: `${item.value}(${item.text})`,
|
||||
value: item.value,
|
||||
})),
|
||||
);
|
||||
}
|
||||
getAggTypeList()
|
||||
|
||||
const changeWindow = (val: boolean | string | number) => {
|
||||
if (val as boolean) {
|
||||
props.value.type = 'window'
|
||||
if (!props.value.window) {
|
||||
props.value.window = {}
|
||||
}
|
||||
} else {
|
||||
delete props.value.type
|
||||
}
|
||||
}
|
||||
|
||||
const windowTypeEnum = [
|
||||
{ label: '时间窗口', value: 'time' },
|
||||
{ label: '次数窗口', value: 'num' },
|
||||
]
|
||||
|
||||
const spanLabel = computed(() => {
|
||||
switch(props.value.windowType) {
|
||||
case 'time':
|
||||
return '窗口长度(秒)';
|
||||
case 'num':
|
||||
return '窗口长度(次)';
|
||||
default:
|
||||
return '窗口长度'
|
||||
}
|
||||
})
|
||||
|
||||
const everyLabel = computed(() => {
|
||||
switch(props.value.windowType) {
|
||||
case 'time':
|
||||
return '步长(秒)';
|
||||
case 'num':
|
||||
return '步长(次)';
|
||||
default:
|
||||
return '步长'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
|
@ -90,6 +90,11 @@ const insert = (val) => {
|
|||
]);
|
||||
}
|
||||
|
||||
watch(() => props.value,
|
||||
(val) => {
|
||||
instance.setValue(val)
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
editorFormat,
|
||||
insert,
|
||||
|
|
|
@ -264,7 +264,7 @@ const JTable = defineComponent<JTableProps>({
|
|||
/**
|
||||
* 导出方法
|
||||
*/
|
||||
expose({ reload })
|
||||
expose({ reload, _dataSource })
|
||||
|
||||
return () => <Spin spinning={loading.value}>
|
||||
<div class={styles["jtable-body"]} style={{ ...props.bodyStyle }}>
|
||||
|
|
|
@ -1,287 +0,0 @@
|
|||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<div class="jtable-body">
|
||||
<div class="jtable-body-header">
|
||||
<div class="jtable-body-header-left">
|
||||
<slot name="headerTitle"></slot>
|
||||
</div>
|
||||
<div class="jtable-body-header-right" v-if="!model">
|
||||
<div class="jtable-setting-item" :class="[ModelEnum.CARD === _model ? 'active' : '']" @click="modelChange(ModelEnum.CARD)">
|
||||
<AppstoreOutlined />
|
||||
</div>
|
||||
<div class="jtable-setting-item" :class="[ModelEnum.TABLE === _model ? 'active' : '']" @click="modelChange(ModelEnum.TABLE)">
|
||||
<UnorderedListOutlined />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="jtable-content">
|
||||
<div class="jtable-alert" v-if="rowSelection && rowSelection.selectedRowKeys && rowSelection.selectedRowKeys.length">
|
||||
<a-alert :message="'已选择' + rowSelection.selectedRowKeys.length + '项'" type="info" :afterClose="handleAlertClose">
|
||||
<template #closeText>
|
||||
<a>取消选择</a>
|
||||
</template>
|
||||
</a-alert>
|
||||
</div>
|
||||
<div v-if="_model === ModelEnum.CARD" class="jtable-card">
|
||||
<div
|
||||
v-if="_dataSource.length"
|
||||
class="jtable-card-items"
|
||||
:style="{gridTemplateColumns: `repeat(${column}, 1fr)`}"
|
||||
>
|
||||
<div
|
||||
class="jtable-card-item"
|
||||
v-for="(item, index) in _dataSource"
|
||||
:key="index"
|
||||
>
|
||||
<slot name="card" v-bind="item" :index="index"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-table rowKey="operationId" :rowSelection="rowSelection" :columns="[..._columns]" :dataSource="_dataSource" :pagination="false">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<!-- <template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-tooltip v-for="i in actions" :key="i.key" v-bind="i.tooltip">
|
||||
<a-popconfirm v-if="i.popConfirm" v-bind="i.popConfirm">
|
||||
<a><AIcon :type="i.icon" /></a>
|
||||
</a-popconfirm>
|
||||
<a v-else @click="i.onClick && i.onClick(record)">
|
||||
<AIcon :type="i.icon" />
|
||||
</a>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template> -->
|
||||
<template v-if="column.scopedSlots">
|
||||
<slot :name="column.key" :row="record"></slot>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="jtable-pagination" v-if="_dataSource.length && !noPagination">
|
||||
<a-pagination
|
||||
size="small"
|
||||
:total="total"
|
||||
:showQuickJumper="false"
|
||||
:showSizeChanger="true"
|
||||
v-model:current="pageIndex"
|
||||
v-model:page-size="pageSize"
|
||||
:show-total="(total, range) => `第 ${range[0]} - ${range[1]} 条/总共 ${total} 条`"
|
||||
@change="pageChange"
|
||||
:page-size-options="[12, 24, 48, 60, 100]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="JTable">
|
||||
import { UnorderedListOutlined, AppstoreOutlined } from '@ant-design/icons-vue'
|
||||
import type { TableProps, ColumnsType } from 'ant-design-vue/es/table'
|
||||
import type { TooltipProps } from 'ant-design-vue/es/tooltip'
|
||||
import type { PopconfirmProps } from 'ant-design-vue/es/popconfirm'
|
||||
import { Empty } from 'ant-design-vue'
|
||||
import { CSSProperties } from 'vue';
|
||||
|
||||
enum ModelEnum {
|
||||
TABLE = 'TABLE',
|
||||
CARD = 'CARD',
|
||||
}
|
||||
|
||||
type RequestData = {
|
||||
code: string;
|
||||
result: {
|
||||
data: Record<string, any>[] | undefined;
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
};
|
||||
status: number;
|
||||
} & Record<string, any>;
|
||||
|
||||
export interface ActionsType {
|
||||
key: string;
|
||||
text?: string;
|
||||
disabled?: boolean;
|
||||
permission?: boolean;
|
||||
onClick?: (data: any) => void;
|
||||
style?: CSSProperties;
|
||||
tooltip?: TooltipProps;
|
||||
popConfirm?: PopconfirmProps;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export interface JColumnsProps extends ColumnsType{
|
||||
scopedSlots?: boolean; // 是否为插槽 true: 是 false: 否
|
||||
}
|
||||
|
||||
export interface JTableProps extends TableProps{
|
||||
request?: (params: Record<string, any> & {
|
||||
pageSize: number;
|
||||
pageIndex: number;
|
||||
}) => Promise<Partial<RequestData>>;
|
||||
cardBodyClass?: string;
|
||||
columns: JColumnsProps;
|
||||
params?: Record<string, any> & {
|
||||
pageSize: number;
|
||||
pageIndex: number;
|
||||
};
|
||||
model?: keyof typeof ModelEnum | undefined; // 显示table还是card
|
||||
actions?: ActionsType[];
|
||||
noPagination?: boolean;
|
||||
rowSelection?: TableProps['rowSelection'];
|
||||
cardProps?: Record<string, any>;
|
||||
dataSource?: Record<string, any>[];
|
||||
}
|
||||
// props
|
||||
const props = withDefaults(defineProps<JTableProps>(), {
|
||||
cardBodyClass: '',
|
||||
request: undefined,
|
||||
})
|
||||
|
||||
// emit
|
||||
const emit = defineEmits<{
|
||||
(e: 'cancelSelect'): void
|
||||
}>()
|
||||
|
||||
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE
|
||||
|
||||
const _model = ref<keyof typeof ModelEnum>(props.model ? props.model : ModelEnum.CARD); // 模式切换
|
||||
const column = ref<number>(4);
|
||||
const _dataSource = ref<Record<string, any>[]>([])
|
||||
const pageIndex = ref<number>(0)
|
||||
const pageSize = ref<number>(6)
|
||||
const total = ref<number>(0)
|
||||
const _columns = ref<Record<string, any>[]>([...props.columns])
|
||||
const loading = ref<boolean>(true)
|
||||
|
||||
// 方法
|
||||
// 切换卡片和表格
|
||||
const modelChange = (type: keyof typeof ModelEnum) => {
|
||||
_model.value = type
|
||||
}
|
||||
/**
|
||||
* 请求数据
|
||||
*/
|
||||
const handleSearch = async (_params?: Record<string, any>) => {
|
||||
loading.value = true
|
||||
if(props.request) {
|
||||
const resp = await props.request({
|
||||
pageSize: 12,
|
||||
pageIndex: 1,
|
||||
..._params
|
||||
})
|
||||
if(resp.status === 200){
|
||||
// 判断如果是最后一页且最后一页为空,就跳转到前一页
|
||||
if(resp.result?.data?.length === 0 && resp.result.total && resp.result.pageSize && resp.result.pageIndex) {
|
||||
handleSearch({
|
||||
..._params,
|
||||
pageSize: pageSize.value,
|
||||
pageIndex: pageIndex.value - 1,
|
||||
})
|
||||
} else {
|
||||
_dataSource.value = resp.result?.data || []
|
||||
pageIndex.value = resp.result?.pageIndex || 0
|
||||
pageSize.value = resp.result?.pageSize || 6
|
||||
total.value = resp.result?.total || 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_dataSource.value = props?.dataSource || []
|
||||
}
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
/**
|
||||
* 页码变化
|
||||
*/
|
||||
const pageChange = (page: number, size: number) => {
|
||||
handleSearch({
|
||||
...props.params,
|
||||
pageSize: size,
|
||||
pageIndex: pageSize.value === size ? page : 1,
|
||||
})
|
||||
}
|
||||
|
||||
// alert关闭,取消选择
|
||||
const handleAlertClose = () => {
|
||||
emit('cancelSelect')
|
||||
}
|
||||
|
||||
// watchEffect(() => {
|
||||
// if(Array.isArray(props.actions) && props.actions.length) {
|
||||
// _columns.value = [...props.columns,
|
||||
// {
|
||||
// title: '操作',
|
||||
// key: 'action',
|
||||
// fixed: 'right',
|
||||
// width: 250
|
||||
// }
|
||||
// ]
|
||||
// } else {
|
||||
// _columns.value = [...props.columns]
|
||||
// }
|
||||
// })
|
||||
|
||||
watchEffect(() => {
|
||||
handleSearch(props.params)
|
||||
})
|
||||
// TODO 选择的双向绑定和图标的渲染
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.jtable-body {
|
||||
width: 100%;
|
||||
padding: 0 24px 24px;
|
||||
background-color: white;
|
||||
.jtable-body-header {
|
||||
padding: 16px 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.jtable-body-header-right {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
.jtable-setting-item {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color-hover;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: @primary-color-active;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.jtable-content {
|
||||
.jtable-alert {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.jtable-card {
|
||||
.jtable-card-items {
|
||||
display: grid;
|
||||
grid-gap: 26px;
|
||||
.jtable-card-item {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.jtable-pagination {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
/deep/ .ant-pagination-item {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -4,6 +4,8 @@ import { filterAsnycRouter, MenuItem } from '@/utils/menu'
|
|||
import { isArray } from 'lodash-es'
|
||||
import { usePermissionStore } from './permission'
|
||||
import router from '@/router'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { onlyMessage } from '@/utils/comm'
|
||||
|
||||
const defaultOwnParams = [
|
||||
{
|
||||
|
@ -77,6 +79,7 @@ export const useMenuStore = defineStore({
|
|||
name, params, query
|
||||
})
|
||||
} else {
|
||||
onlyMessage('暂无权限,请联系管理员', 'error')
|
||||
console.warn(`没有找到对应的页面: ${name}`)
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Search :columns="columns" target="northbound-aliyun" @search="handleSearch" />
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="northbound-aliyun"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JTable
|
||||
ref="instanceRef"
|
||||
:columns="columns"
|
||||
|
@ -24,22 +28,15 @@
|
|||
<CardBox
|
||||
:value="slotProps"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
v-bind="slotProps"
|
||||
:status="slotProps.state?.value"
|
||||
:statusText="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
enabled: 'success',
|
||||
disabled: 'error'
|
||||
disabled: 'error',
|
||||
}"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img
|
||||
:src="
|
||||
getImage('/northbound/aliyun.png')
|
||||
"
|
||||
/>
|
||||
</slot>
|
||||
<img :src="getImage('/northbound/aliyun.png')" />
|
||||
</template>
|
||||
<template #content>
|
||||
<h3
|
||||
|
@ -114,23 +111,18 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
query,
|
||||
_undeploy,
|
||||
_deploy,
|
||||
_delete
|
||||
} from '@/api/northbound/alicloud';
|
||||
import { query, _undeploy, _deploy, _delete } from '@/api/northbound/alicloud';
|
||||
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');
|
||||
|
@ -167,7 +159,7 @@ const columns = [
|
|||
type: 'select',
|
||||
options: [
|
||||
{ label: '正常', value: 'enabled' },
|
||||
{ label: '禁用', value: 'disabled' }
|
||||
{ label: '禁用', value: 'disabled' },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -184,14 +176,14 @@ const columns = [
|
|||
* 新增
|
||||
*/
|
||||
const handleAdd = () => {
|
||||
menuStory.jumpPage('Northbound/AliCloud/Detail', { id: ':id'})
|
||||
menuStory.jumpPage('Northbound/AliCloud/Detail', { id: ':id' });
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
const handleView = (id: string) => {
|
||||
menuStory.jumpPage('Northbound/AliCloud/Detail', { id }, { type: 'view'})
|
||||
menuStory.jumpPage('Northbound/AliCloud/Detail', { id }, { type: 'view' });
|
||||
};
|
||||
|
||||
const getActions = (
|
||||
|
@ -219,7 +211,11 @@ const getActions = (
|
|||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
menuStory.jumpPage('Northbound/AliCloud/Detail', { id: data.id }, { type: 'edit'})
|
||||
menuStory.jumpPage(
|
||||
'Northbound/AliCloud/Detail',
|
||||
{ id: data.id },
|
||||
{ type: 'edit' },
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -283,6 +279,6 @@ const getActions = (
|
|||
};
|
||||
|
||||
const handleSearch = (_params: any) => {
|
||||
params.value = _params
|
||||
}
|
||||
params.value = _params;
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
<CardBox
|
||||
:value="slotProps"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
v-bind="slotProps"
|
||||
:status="slotProps.state?.value"
|
||||
:statusText="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
|
@ -37,9 +36,7 @@
|
|||
}"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img :src="getImage('/cloud/dueros.png')" />
|
||||
</slot>
|
||||
<img :src="getImage('/cloud/dueros.png')" />
|
||||
</template>
|
||||
<template #content>
|
||||
<h3
|
||||
|
|
|
@ -183,7 +183,6 @@
|
|||
<script lang="ts" setup>
|
||||
import { message, Form } from 'ant-design-vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import type { UploadChangeParam } from 'ant-design-vue';
|
||||
import FileUpload from './FileUpload.vue';
|
||||
import { save, update, queryProduct } from '@/api/device/firmware';
|
||||
import type { FormInstance } from 'ant-design-vue';
|
||||
|
@ -260,7 +259,7 @@ const { resetFields, validate, validateInfos } = useForm(
|
|||
{ required: true, message: '请输入版本号' },
|
||||
{ max: 64, message: '最多可输入64个字符', trigger: 'change' },
|
||||
],
|
||||
versionOrder: [{ required: true, message: '请输入版本号' }],
|
||||
versionOrder: [{ required: true, message: '请输入版本序号' }],
|
||||
signMethod: [{ required: true, message: '请选择签名方式' }],
|
||||
sign: [
|
||||
{ required: true, message: '请输入签名' },
|
||||
|
|
|
@ -0,0 +1,272 @@
|
|||
<template lang="">
|
||||
<a-input
|
||||
placeholder="请选择设备"
|
||||
v-model:value="checkLable"
|
||||
:disabled="true"
|
||||
>
|
||||
<template #addonAfter>
|
||||
<AIcon type="EditOutlined" @click="onVisible" />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-modal
|
||||
v-if="visible"
|
||||
title="选择设备"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
:visible="true"
|
||||
width="80%"
|
||||
@cancel="handleCancel"
|
||||
@ok="handleOk"
|
||||
>
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="search"
|
||||
@search="handleSearch"
|
||||
type="simple"
|
||||
/>
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
model="TABLE"
|
||||
:columns="columns"
|
||||
:request="queryDetailList"
|
||||
:defaultParams="defaultParams"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
onSelect: onSelectChange,
|
||||
onSelectAll: onSelectAllChange,
|
||||
}"
|
||||
@cancelSelect="cancelSelect"
|
||||
:params="params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-checkbox
|
||||
v-model:checked="state.checkAll"
|
||||
:indeterminate="state.indeterminate"
|
||||
@change="onCheckAllChange"
|
||||
style="margin-left: 8px"
|
||||
>
|
||||
全选
|
||||
</a-checkbox>
|
||||
</template>
|
||||
<template #productId="slotProps">
|
||||
<span>{{ slotProps.productName }}</span>
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
:text="slotProps.state?.text"
|
||||
:status="statusMap.get(slotProps.state?.value)"
|
||||
/>
|
||||
</template>
|
||||
<template #version="slotProps">
|
||||
<span>{{ slotProps.firmwareInfo?.version || '' }}</span>
|
||||
</template>
|
||||
<template #registerTime="slotProps">
|
||||
<span>{{
|
||||
moment(slotProps.registerTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}}</span>
|
||||
</template>
|
||||
</JTable>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script lang="ts" setup name="SelectDevicesPage">
|
||||
import {
|
||||
queryDetailListNoPaging,
|
||||
queryDetailList,
|
||||
} from '@/api/device/firmware';
|
||||
import moment from 'moment';
|
||||
|
||||
type T = any;
|
||||
const emit = defineEmits(['update:modelValue', 'change']);
|
||||
|
||||
const route = useRoute();
|
||||
const params = ref<Record<string, any>>({});
|
||||
const visible = ref(false);
|
||||
const _selectedRowKeys = ref<string[]>([]);
|
||||
|
||||
const state = reactive({
|
||||
indeterminate: false,
|
||||
checkAll: false,
|
||||
checkedList: [],
|
||||
});
|
||||
let checkAllData: T[] = [];
|
||||
const checkAllDataMap = new Map();
|
||||
const checkLable = ref();
|
||||
|
||||
const defaultParams = {
|
||||
context: {
|
||||
includeTags: false,
|
||||
includeBind: false,
|
||||
includeRelations: false,
|
||||
},
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'productId',
|
||||
value: route.query.productId,
|
||||
},
|
||||
],
|
||||
type: 'and',
|
||||
},
|
||||
],
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
};
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('online', 'success');
|
||||
statusMap.set('offline', 'error');
|
||||
statusMap.set('notActive', 'warning');
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
dataIndex: 'id',
|
||||
fixed: 'left',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '设备名称',
|
||||
key: 'name',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '固件版本',
|
||||
dataIndex: 'version',
|
||||
key: 'version',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '注册时间',
|
||||
key: 'registerTime',
|
||||
dataIndex: 'registerTime',
|
||||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
width: 200,
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '在线', value: 'online' },
|
||||
{ label: '离线', value: 'offline' },
|
||||
{ label: '禁用', value: 'notActive' },
|
||||
],
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
];
|
||||
|
||||
const onCheckAllChange = (e: any) => {
|
||||
Object.assign(state, {
|
||||
checkedList: e.target.checked ? checkAllData : [],
|
||||
indeterminate: false,
|
||||
});
|
||||
_selectedRowKeys.value = state.checkedList;
|
||||
};
|
||||
|
||||
const onSelectChange = (record: T[], selected: boolean, selectedRows: T[]) => {
|
||||
_selectedRowKeys.value = selected
|
||||
? [...getSetRowKey(selectedRows)]
|
||||
: _selectedRowKeys.value.filter((item: T) => item !== record?.id);
|
||||
};
|
||||
const onSelectAllChange = (
|
||||
selected: boolean,
|
||||
selectedRows: T[],
|
||||
changeRows: T[],
|
||||
) => {
|
||||
const unRowsKeys = getSelectedRowsKey(changeRows);
|
||||
_selectedRowKeys.value = selected
|
||||
? [...getSetRowKey(selectedRows)]
|
||||
: _selectedRowKeys.value
|
||||
.concat(unRowsKeys)
|
||||
.filter((item) => !unRowsKeys.includes(item));
|
||||
};
|
||||
|
||||
const getSelectedRowsKey = (selectedRows: T[]) =>
|
||||
selectedRows.map((item) => item?.id).filter((i) => !!i);
|
||||
|
||||
const getSetRowKey = (selectedRows: T[]) =>
|
||||
new Set([..._selectedRowKeys.value, ...getSelectedRowsKey(selectedRows)]);
|
||||
|
||||
const cancelSelect = () => {
|
||||
_selectedRowKeys.value = [];
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
checkLable.value = _selectedRowKeys.value
|
||||
.map((item) => checkAllDataMap.has(item) && checkAllDataMap.get(item))
|
||||
.toString();
|
||||
emit('update:modelValue', _selectedRowKeys.value);
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
const onVisible = () => {
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
visible.value = false;
|
||||
cancelSelect();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
queryDetailListNoPaging({ ...defaultParams, paging: false }).then(
|
||||
(resp: T) => {
|
||||
if (resp.status === 200) {
|
||||
checkAllData = resp.result.map((item: T) => {
|
||||
checkAllDataMap.set(item.id, item.name);
|
||||
return item.id;
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => _selectedRowKeys.value,
|
||||
(val) => {
|
||||
Object.assign(state, {
|
||||
checkedList: val,
|
||||
indeterminate: !!val.length && val.length < checkAllData.length,
|
||||
checkAll:
|
||||
!!checkAllData.length && val.length === checkAllData.length,
|
||||
});
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* @param params
|
||||
*/
|
||||
const handleSearch = (e: any) => {
|
||||
params.value = e;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.form {
|
||||
.form-submit {
|
||||
background-color: @primary-color !important;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,277 @@
|
|||
<template lang="">
|
||||
<a-modal
|
||||
:title="data.id ? '查看' : '新增' + '任务'"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
:visible="true"
|
||||
width="700px"
|
||||
:confirm-loading="loading"
|
||||
@cancel="handleCancel"
|
||||
@ok="handleOk"
|
||||
>
|
||||
<a-form
|
||||
class="form"
|
||||
layout="vertical"
|
||||
:model="formData"
|
||||
name="basic"
|
||||
autocomplete="off"
|
||||
>
|
||||
<a-row :gutter="[24, 0]">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="任务名称" v-bind="validateInfos.name">
|
||||
<a-input
|
||||
placeholder="请输入任务名称"
|
||||
v-model:value="formData.name"
|
||||
/></a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24"
|
||||
><a-form-item label="推送方式" v-bind="validateInfos.mode">
|
||||
<a-select
|
||||
v-model:value="formData.mode"
|
||||
:options="[
|
||||
{ label: '平台推送', value: 'push' },
|
||||
{ label: '设备拉取', value: 'pull' },
|
||||
]"
|
||||
placeholder="请选择推送方式"
|
||||
allowClear
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
@change="changeMode"
|
||||
/> </a-form-item
|
||||
></a-col>
|
||||
<a-col :span="12" v-if="formData.mode === 'push'"
|
||||
><a-form-item
|
||||
label="响应超时时间"
|
||||
v-bind="validateInfos.responseTimeoutSeconds"
|
||||
>
|
||||
<a-input-number
|
||||
placeholder="请输入响应超时时间(秒)"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="99999"
|
||||
v-model:value="
|
||||
formData.responseTimeoutSeconds
|
||||
" /></a-form-item
|
||||
></a-col>
|
||||
<a-col
|
||||
:span="formData.mode === 'push' ? 12 : 24"
|
||||
v-if="formData.mode === 'push' || formData.mode === 'pull'"
|
||||
><a-form-item
|
||||
label="升级超时时间"
|
||||
v-bind="validateInfos.timeoutSeconds"
|
||||
>
|
||||
<a-input-number
|
||||
placeholder="请输入升级超时时间(秒)"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="99999"
|
||||
v-model:value="
|
||||
formData.timeoutSeconds
|
||||
" /></a-form-item
|
||||
></a-col>
|
||||
<a-col :span="12" v-if="!!formData.mode"
|
||||
><a-form-item
|
||||
label="升级设备"
|
||||
v-bind="validateInfos.releaseType"
|
||||
>
|
||||
<a-radio-group
|
||||
v-model:value="formData.releaseType"
|
||||
button-style="solid"
|
||||
@change="changeShareCluster"
|
||||
>
|
||||
<a-radio value="all">所有设备</a-radio>
|
||||
<a-radio value="part">选择设备</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12" v-if="formData.releaseType === 'part'"
|
||||
><a-form-item
|
||||
label="选择设备"
|
||||
v-bind="validateInfos.deviceId"
|
||||
>
|
||||
<SelectDevices
|
||||
v-model:modelValue="formData.deviceId"
|
||||
:data="devicesData"
|
||||
></SelectDevices> </a-form-item
|
||||
></a-col>
|
||||
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
label="说明"
|
||||
v-bind="validateInfos.description"
|
||||
>
|
||||
<a-textarea
|
||||
placeholder="请输入说明"
|
||||
v-model:value="formData.description"
|
||||
:maxlength="200"
|
||||
:rows="3"
|
||||
showCount
|
||||
/> </a-form-item
|
||||
></a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<!-- <SelectDevices v-if="visible" @change="saveChange"></SelectDevices> -->
|
||||
</template>
|
||||
<script lang="ts" setup name="TaskPage">
|
||||
import { message, Form } from 'ant-design-vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { save, update, queryProduct } from '@/api/device/firmware';
|
||||
import type { FormInstance } from 'ant-design-vue';
|
||||
import type { Properties } from '../../type';
|
||||
import SelectDevices from './SelectDevices.vue';
|
||||
|
||||
const route = useRoute();
|
||||
console.log(111, route.query);
|
||||
|
||||
const loading = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
const productOptions = ref([]);
|
||||
const visible = ref(false);
|
||||
const devicesData = ref()
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
const id = props.data.id;
|
||||
|
||||
const formData = ref({
|
||||
name: '',
|
||||
mode: undefined,
|
||||
responseTimeoutSeconds: '',
|
||||
timeoutSeconds: '',
|
||||
releaseType: 'all',
|
||||
deviceId: [],
|
||||
description: '',
|
||||
});
|
||||
|
||||
const extraValue = ref({});
|
||||
|
||||
const validatorSign = async (_: Record<string, any>, value: string) => {
|
||||
// const { releaseType, url } = formData.value;
|
||||
// if (value && !!releaseType && !!url && !extraValue.value) {
|
||||
// return extraValue.value[releaseType] !== value
|
||||
// ? Promise.reject('选择设备不一致,请检查文件是否上传正确')
|
||||
// : Promise.resolve();
|
||||
// } else {
|
||||
// return Promise.resolve();
|
||||
// }
|
||||
};
|
||||
|
||||
const { resetFields, validate, validateInfos } = useForm(
|
||||
formData,
|
||||
reactive({
|
||||
name: [
|
||||
{ required: true, message: '请输入任务名称' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
mode: [{ required: true, message: '请选择推送方式' }],
|
||||
responseTimeoutSeconds: [
|
||||
{ required: true, message: '请输入响应超时时间' },
|
||||
],
|
||||
timeoutSeconds: [{ required: true, message: '请输入升级超时时间' }],
|
||||
releaseType: [{ required: true }],
|
||||
deviceId: [
|
||||
{ required: true, message: '请选择设备' },
|
||||
{ validator: validatorSign },
|
||||
],
|
||||
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||
}),
|
||||
);
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
const changeMode = (value) => {
|
||||
console.log(111, value, formData.value);
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
visible.value = true;
|
||||
};
|
||||
const saveChange = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
validate()
|
||||
.then(async (res) => {
|
||||
// const product = productOptions.value.find(
|
||||
// (item) => item.value === res.mode,
|
||||
// );
|
||||
// const productName = product.label || props.data?.url;
|
||||
// const size = extraValue.value.length || props.data?.size;
|
||||
// const params = {
|
||||
// ...toRaw(formData.value),
|
||||
// properties: !!properties ? properties : [],
|
||||
// productName,
|
||||
// size,
|
||||
// };
|
||||
// loading.value = true;
|
||||
// const response = !id
|
||||
// ? await save(params)
|
||||
// : await update({ ...props.data, ...params });
|
||||
// if (response.status === 200) {
|
||||
// message.success('操作成功');
|
||||
// emit('change', true);
|
||||
// }
|
||||
// loading.value = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
onSubmit();
|
||||
};
|
||||
const handleCancel = () => {
|
||||
emit('change', false);
|
||||
};
|
||||
|
||||
const changeSignMethod = () => {
|
||||
formData.value.deviceId = '';
|
||||
formData.value.url = '';
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
queryProduct({
|
||||
paging: false,
|
||||
terms: [{ column: 'state', value: 1 }],
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
}).then((resp) => {
|
||||
productOptions.value = resp.result.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
}));
|
||||
});
|
||||
});
|
||||
watch(
|
||||
() => props.data,
|
||||
(value) => {
|
||||
if (value.id) {
|
||||
formData.value = value;
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
watch(
|
||||
() => extraValue.value,
|
||||
() => validate('deviceId'),
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.form {
|
||||
.form-submit {
|
||||
background-color: @primary-color !important;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,256 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Search :columns="columns" target="search" @search="handleSearch" />
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
model="TABLE"
|
||||
:columns="columns"
|
||||
:request="task"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
terms: defaultParams,
|
||||
}"
|
||||
:params="params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-button type="primary" @click="handlAdd"
|
||||
><AIcon type="PlusOutlined" />新增</a-button
|
||||
>
|
||||
</template>
|
||||
<template #mode="slotProps">
|
||||
<span>{{ slotProps.mode.text }}</span>
|
||||
</template>
|
||||
<template #progress="slotProps">
|
||||
<span>{{ slotProps.progress }}%</span>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps)"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm v-if="i.popConfirm" v-bind="i.popConfirm">
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
<Save v-if="visible" :data="current" @change="saveChange" />
|
||||
</page-container>
|
||||
</template>
|
||||
<script lang="ts" setup name="CertificatePage">
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { task, startTask, stopTask } from '@/api/device/firmware';
|
||||
import { message } from 'ant-design-vue';
|
||||
import Save from './Save/index.vue'
|
||||
|
||||
const tableRef = ref<Record<string, any>>({});
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const params = ref<Record<string, any>>({});
|
||||
|
||||
const visible = ref(false);
|
||||
const current = ref({});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '任务名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
fixed: 'left',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
// scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '推送方式',
|
||||
dataIndex: 'mode',
|
||||
key: 'mode',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: '设备拉取',
|
||||
value: 'pull',
|
||||
},
|
||||
{
|
||||
label: '平台推送',
|
||||
value: 'push',
|
||||
},
|
||||
],
|
||||
},
|
||||
scopedSlots: true,
|
||||
width: 200,
|
||||
},
|
||||
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '完成比例',
|
||||
dataIndex: 'progress',
|
||||
key: 'progress',
|
||||
ellipsis: true,
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
|
||||
const defaultParams = [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'firmwareId',
|
||||
value: route.query.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const stop = data.waiting > 0 && data?.state?.value === 'processing';
|
||||
const pause = data?.state?.value === 'canceled';
|
||||
|
||||
const Actions = [
|
||||
{
|
||||
key: 'edit',
|
||||
text: '详情',
|
||||
tooltip: {
|
||||
title: '详情',
|
||||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: async () => {
|
||||
handlEdit(data.id);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'eye',
|
||||
text: '查看',
|
||||
tooltip: {
|
||||
title: '查看',
|
||||
},
|
||||
icon: 'EyeOutlined',
|
||||
onClick: async () => {
|
||||
handlEye(data);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (stop) {
|
||||
Actions.push({
|
||||
key: 'actions',
|
||||
text: '停止',
|
||||
tooltip: {
|
||||
title: '停止',
|
||||
},
|
||||
onClick: async () => {
|
||||
const res = await stopTask(data.id);
|
||||
if (res.success) {
|
||||
message.success('操作成功');
|
||||
tableRef.value.reload();
|
||||
}
|
||||
},
|
||||
icon: 'StopOutlined',
|
||||
});
|
||||
} else if (pause) {
|
||||
Actions.push({
|
||||
key: 'actions',
|
||||
text: '继续升级',
|
||||
tooltip: {
|
||||
title: '继续升级',
|
||||
},
|
||||
onClick: async () => {
|
||||
const res = await startTask(data.id, ['canceled']);
|
||||
if (res.success) {
|
||||
message.success('操作成功');
|
||||
tableRef.value.reload();
|
||||
}
|
||||
},
|
||||
icon: 'PauseOutlined',
|
||||
});
|
||||
}
|
||||
|
||||
return Actions;
|
||||
};
|
||||
|
||||
const handlAdd = () => {
|
||||
current.value = {};
|
||||
visible.value = true;
|
||||
|
||||
};
|
||||
|
||||
const handlEye = (data: object) => {
|
||||
current.value = toRaw(data);
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
const handlEdit = (id: string) => {
|
||||
// router.push({
|
||||
// path: `/iot/link/certificate/detail/${id}`,
|
||||
// query: { view: false },
|
||||
// });
|
||||
};
|
||||
const saveChange = (value: FormDataType) => {
|
||||
visible.value = false;
|
||||
current.value = {};
|
||||
if (value) {
|
||||
message.success('操作成功');
|
||||
tableRef.value.reload();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* @param params
|
||||
*/
|
||||
const handleSearch = (e: any) => {
|
||||
params.value = e;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.a {
|
||||
// display: none;
|
||||
visibility: 0;
|
||||
}
|
||||
</style>
|
|
@ -66,14 +66,17 @@
|
|||
<Save v-if="visible" :data="current" @change="saveChange" />
|
||||
</page-container>
|
||||
</template>
|
||||
<script lang="ts" setup name="CertificatePage">
|
||||
<script lang="ts" setup name="FirmwarePage">
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
// import { save, query, remove } from '@/api/link/certificate';
|
||||
import { query, queryProduct, remove } from '@/api/device/firmware';
|
||||
import { message } from 'ant-design-vue';
|
||||
import moment from 'moment';
|
||||
import _ from 'lodash';
|
||||
import Save from './Save/index.vue';
|
||||
import { useMenuStore } from 'store/menu';
|
||||
import type { FormDataType } from './type';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const tableRef = ref<Record<string, any>>({});
|
||||
const router = useRouter();
|
||||
|
@ -178,7 +181,7 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
|
|||
},
|
||||
icon: 'FileTextOutlined',
|
||||
onClick: async () => {
|
||||
handlUpdate(data.id);
|
||||
handlUpdate(data);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -208,23 +211,27 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
|
|||
];
|
||||
};
|
||||
|
||||
const handlUpdate = (id: string) => {
|
||||
// router.push({
|
||||
// path: `/iot/link/certificate/detail/${id}`,
|
||||
// query: { view: true },
|
||||
// });
|
||||
const handlUpdate = (data: FormDataType) => {
|
||||
menuStory.jumpPage(
|
||||
'device/Firmware/Task',
|
||||
{},
|
||||
{
|
||||
id: data.id,
|
||||
productId: data.productId,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handlAdd = () => {
|
||||
current.value = {};
|
||||
visible.value = true;
|
||||
};
|
||||
const handlEdit = (data: object) => {
|
||||
const handlEdit = (data: FormDataType) => {
|
||||
current.value = _.cloneDeep(data);
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
const saveChange = (value: object) => {
|
||||
const saveChange = (value: FormDataType) => {
|
||||
visible.value = false;
|
||||
current.value = {};
|
||||
if (value) {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import AIcon from "@/components/AIcon";
|
||||
import { useInstanceStore } from "@/store/instance";
|
||||
import { useMenuStore } from "@/store/menu";
|
||||
import { Button, Descriptions, Modal } from "ant-design-vue"
|
||||
import styles from './index.module.less'
|
||||
|
||||
|
@ -14,6 +16,10 @@ const ManualInspection = defineComponent({
|
|||
|
||||
const { data } = props
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const dataRender = () => {
|
||||
if (data.type === 'device' || data.type === 'product') {
|
||||
return (
|
||||
|
@ -207,7 +213,13 @@ const ManualInspection = defineComponent({
|
|||
emit('save', data)
|
||||
}}
|
||||
onCancel={() => {
|
||||
// TODO 跳转设备和产品
|
||||
if (data.type === 'device') {
|
||||
instanceStore.tabActiveKey = 'Info'
|
||||
} else if (data.type === 'product') {
|
||||
menuStory.jumpPage('device/Product/Detail', { id: data.productId, tab: 'access' });
|
||||
} else {
|
||||
menuStory.jumpPage('link/AccessConfig/Detail', { id: data.configuration?.id });
|
||||
}
|
||||
}}>
|
||||
<div style={{ display: 'flex' }}>{dataRender()}</div>
|
||||
</Modal>
|
||||
|
|
|
@ -11,6 +11,8 @@ import _ from "lodash"
|
|||
import DiagnosticAdvice from './DiagnosticAdvice'
|
||||
import ManualInspection from './ManualInspection'
|
||||
import { deployDevice } from "@/api/initHome"
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue'
|
||||
import { useMenuStore } from "@/store/menu"
|
||||
|
||||
type TypeProps = 'network' | 'child-device' | 'media' | 'cloud' | 'channel'
|
||||
|
||||
|
@ -41,6 +43,7 @@ const Status = defineComponent({
|
|||
const diagnoseData = ref<Partial<Record<string, any>>>()
|
||||
|
||||
const bindParentVisible = ref<boolean>(false)
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const configuration = reactive<{
|
||||
product: Record<string, any>,
|
||||
|
@ -57,19 +60,8 @@ const Status = defineComponent({
|
|||
artificialData.value = params
|
||||
}
|
||||
|
||||
// TODO
|
||||
const jumpAccessConfig = () => {
|
||||
// const purl = getMenuPathByCode(MENUS_CODE['device/Product/Detail']);
|
||||
// if (purl) {
|
||||
// history.push(
|
||||
// `${getMenuPathByParams(MENUS_CODE['device/Product/Detail'], device.productId)}`,
|
||||
// {
|
||||
// tab: 'access',
|
||||
// },
|
||||
// );
|
||||
// } else {
|
||||
// message.error('规则可能有加密处理,请联系管理员');
|
||||
// }
|
||||
menuStory.jumpPage('device/Product/Detail', { id: unref(device).productId, tab: 'access' });
|
||||
};
|
||||
|
||||
const jumpDeviceConfig = () => {
|
||||
|
@ -123,34 +115,40 @@ const Status = defineComponent({
|
|||
<Badge
|
||||
status="default"
|
||||
text={
|
||||
<span>网络组件已禁用,请先<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
const res = await startNetwork(
|
||||
unref(gateway)?.channelId,
|
||||
);
|
||||
if (res.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'network',
|
||||
name: '网络组件',
|
||||
desc: '诊断网络组件配置是否正确,配置错误将导致设备连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm></span>
|
||||
<span>网络组件已禁用,请先
|
||||
<PermissionButton
|
||||
type="link"
|
||||
hasPermission="link/Type:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const res = await startNetwork(
|
||||
unref(gateway)?.channelId,
|
||||
);
|
||||
if (res.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'network',
|
||||
name: '网络组件',
|
||||
desc: '诊断网络组件配置是否正确,配置错误将导致设备连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
启用
|
||||
</PermissionButton>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div >
|
||||
) : (
|
||||
<div>
|
||||
<div class={styles.infoItem}>
|
||||
|
@ -287,28 +285,31 @@ const Status = defineComponent({
|
|||
<Badge
|
||||
status="default"
|
||||
text={<span>设备接入网关已禁用,请先
|
||||
<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
const resp = await startGateway(unref(device).accessId || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: desc,
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
<PermissionButton
|
||||
hasPermission="link/Type:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const resp = await startGateway(unref(device).accessId || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: desc,
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm>
|
||||
启用
|
||||
</PermissionButton>
|
||||
</span>}
|
||||
/>
|
||||
</div>
|
||||
|
@ -411,28 +412,32 @@ const Status = defineComponent({
|
|||
status="default"
|
||||
text={
|
||||
<span>
|
||||
设备接入网关已禁用,请先<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
const resp = await startGateway(unref(device).accessId || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: desc,
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
设备接入网关已禁用,请先
|
||||
<PermissionButton
|
||||
hasPermission="link/AccessConfig:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const resp = await startGateway(unref(device).accessId || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: desc,
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm>
|
||||
启用
|
||||
</PermissionButton>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
@ -519,28 +524,32 @@ const Status = defineComponent({
|
|||
status="default"
|
||||
text={
|
||||
<span>
|
||||
网关父设备已禁用,请先<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
const resp = await _deploy(response?.result?.id || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'parent-device',
|
||||
name: '网关父设备',
|
||||
desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
网关父设备已禁用,请先
|
||||
<PermissionButton
|
||||
hasPermission="device/Product:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const resp = await _deploy(response?.result?.id || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'parent-device',
|
||||
name: '网关父设备',
|
||||
desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm>
|
||||
启用
|
||||
</PermissionButton>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
@ -623,28 +632,32 @@ const Status = defineComponent({
|
|||
status="default"
|
||||
text={
|
||||
<span>
|
||||
产品已禁用,请<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
const resp = await _deployProduct(unref(device).productId || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'product',
|
||||
name: '产品状态',
|
||||
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
产品已禁用,请
|
||||
<PermissionButton
|
||||
hasPermission="device/Product:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const resp = await _deployProduct(unref(device).productId || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'product',
|
||||
name: '产品状态',
|
||||
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm>
|
||||
启用
|
||||
</PermissionButton>
|
||||
产品
|
||||
</span>
|
||||
}
|
||||
|
@ -695,29 +708,34 @@ const Status = defineComponent({
|
|||
status="default"
|
||||
text={
|
||||
<span>
|
||||
设备已禁用,请<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
const resp = await _deploy(unref(device)?.id || '');
|
||||
if (resp.status === 200) {
|
||||
instanceStore.current.state = { value: 'offline', text: '离线' }
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'device',
|
||||
name: '设备状态',
|
||||
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
设备已禁用,请
|
||||
<PermissionButton
|
||||
hasPermission="device/Instance:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const resp = await _deploy(unref(device)?.id || '');
|
||||
if (resp.status === 200) {
|
||||
instanceStore.current.state = { value: 'offline', text: '离线' }
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'device',
|
||||
name: '设备状态',
|
||||
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm>设备
|
||||
启用
|
||||
</PermissionButton>
|
||||
设备
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
:columns="columns"
|
||||
:request="_getEventList"
|
||||
model="TABLE"
|
||||
:bodyStyle="{padding: '0 24px'}"
|
||||
:bodyStyle="{ padding: '0 24px' }"
|
||||
>
|
||||
<template #timestamp="slotProps">
|
||||
{{ moment(slotProps.timestamp).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
|
@ -19,18 +19,18 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import moment from 'moment'
|
||||
import { getEventList } from '@/api/device/instance'
|
||||
import { useInstanceStore } from '@/store/instance'
|
||||
import { Modal } from 'ant-design-vue'
|
||||
import moment from 'moment';
|
||||
import { getEventList } from '@/api/device/instance';
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
const events = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
const instanceStore = useInstanceStore()
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const instanceStore = useInstanceStore();
|
||||
|
||||
const columns = ref<Record<string, any>>([
|
||||
{
|
||||
|
@ -38,43 +38,52 @@ const columns = ref<Record<string, any>>([
|
|||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
scopedSlots: true,
|
||||
}
|
||||
])
|
||||
const params = ref<Record<string, any>>({})
|
||||
},
|
||||
]);
|
||||
const params = ref<Record<string, any>>({});
|
||||
|
||||
const _getEventList = () => getEventList(instanceStore.current.id || '', events.data.id || '', params.value)
|
||||
const _getEventList = () =>
|
||||
getEventList(
|
||||
instanceStore.current.id || '',
|
||||
events.data.id || '',
|
||||
params.value,
|
||||
);
|
||||
|
||||
watchEffect(() => {
|
||||
if(events.data?.valueType?.type === 'object'){
|
||||
if (events.data?.valueType?.type === 'object') {
|
||||
(events.data.valueType?.properties || []).map((i: any) => {
|
||||
columns.value.splice(0, 0, {
|
||||
key: i.id,
|
||||
title: i.name,
|
||||
dataIndex: `${i.id}_format`
|
||||
})
|
||||
})
|
||||
key: i.id,
|
||||
title: i.name,
|
||||
dataIndex: `${i.id}_format`,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
});
|
||||
});
|
||||
} else {
|
||||
columns.value.splice(0, 0, {
|
||||
title: '数据',
|
||||
dataIndex: 'value',
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const detail = () => {
|
||||
Modal.info({
|
||||
title: () => '详情',
|
||||
width: 850,
|
||||
content: () => h('div', {}, [
|
||||
h('p', '暂未开发'),
|
||||
]),
|
||||
okText: '关闭'
|
||||
});
|
||||
}
|
||||
content: () => h('div', {}, [h('p', '暂未开发')]),
|
||||
okText: '关闭',
|
||||
});
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,54 @@
|
|||
<!-- 坐标点拾取组件 -->
|
||||
<template>
|
||||
<div style="width: 100%; height: 400px">
|
||||
<div style="position: relative">
|
||||
<div style="position: absolute; right: 0; top: 5px; z-index: 999">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="start">开始动画</a-button>
|
||||
<a-button type="primary" @click="stop">停止动画</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<el-amap :center="center" :zooms="[3, 20]" @init="initMap" ref="map"></el-amap>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { initAMapApiLoader } from '@vuemap/vue-amap';
|
||||
import AMapUI from '@vuemap/vue-amap'
|
||||
import '@vuemap/vue-amap/dist/style.css';
|
||||
|
||||
initAMapApiLoader({
|
||||
key: 'a0415acfc35af15f10221bfa5a6850b4',
|
||||
securityJsCode: 'cae6108ec3dd222f946d1a7237c78be0',
|
||||
});
|
||||
|
||||
interface EmitProps {
|
||||
(e: 'update:points', data: string): void;
|
||||
}
|
||||
const props = defineProps({
|
||||
points: { type: Array, default: () => [] },
|
||||
});
|
||||
const emit = defineEmits<EmitProps>();
|
||||
|
||||
// 地图拾取的坐标点(经纬度字符串)
|
||||
const mapPoint = ref('');
|
||||
|
||||
const map = ref(null);
|
||||
|
||||
const center = ref([106.55, 29.56]);
|
||||
const marker = ref(null);
|
||||
|
||||
/**
|
||||
* 地图初始化
|
||||
* @param e
|
||||
*/
|
||||
const initMap = (e: any) => {
|
||||
console.log(e)
|
||||
// map = e;
|
||||
// const pointStr = mapPoint.value as string;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<div class="chart" ref="chart"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
|
||||
const props = defineProps({
|
||||
// 图表数据
|
||||
options:{
|
||||
type:Object,
|
||||
default:()=>{}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 绘制图表
|
||||
*/
|
||||
const createChart = () => {
|
||||
nextTick(() => {
|
||||
const myChart = echarts.init(proxy.$refs.chart);
|
||||
myChart.setOption(props.options);
|
||||
window.addEventListener('resize', function () {
|
||||
myChart.resize();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.options,
|
||||
() => createChart(),
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,218 @@
|
|||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<div>
|
||||
<a-space>
|
||||
<div>
|
||||
统计周期:
|
||||
<a-select v-model:value="cycle" style="width: 120px">
|
||||
<a-select-option value="*" v-if="_type"
|
||||
>实际值</a-select-option
|
||||
>
|
||||
<a-select-option value="1m">按分钟统计</a-select-option>
|
||||
<a-select-option value="1h">按小时统计</a-select-option>
|
||||
<a-select-option value="1d">按天统计</a-select-option>
|
||||
<a-select-option value="1w">按周统计</a-select-option>
|
||||
<a-select-option value="1M">按月统计</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<div v-if="cycle !== '*' && _type">
|
||||
统计规则:
|
||||
<a-select v-model:value="agg" style="width: 120px">
|
||||
<a-select-option value="AVG">平均值</a-select-option>
|
||||
<a-select-option value="MAX">最大值</a-select-option>
|
||||
<a-select-option value="MIN">最小值</a-select-option>
|
||||
<a-select-option value="COUNT">总数</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</a-space>
|
||||
</div>
|
||||
<div style="width: 100%; height: 500px">
|
||||
<Chart :options="options" v-if="chartsList.length" />
|
||||
<JEmpty v-else />
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getPropertiesInfo, getPropertiesList } from '@/api/device/instance';
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import Chart from './Chart.vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const list = ['int', 'float', 'double', 'long'];
|
||||
|
||||
const prop = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
time: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const cycle = ref<string>('*');
|
||||
const agg = ref<string>('AVG');
|
||||
const loading = ref<boolean>(false);
|
||||
const chartsList = ref<any[]>([]);
|
||||
const instanceStore = useInstanceStore();
|
||||
const options = ref({});
|
||||
|
||||
const _type = computed(() => {
|
||||
const flag = list.includes(prop.data?.valueType?.type || '')
|
||||
cycle.value = flag ? '*' : '1m'
|
||||
return flag
|
||||
});
|
||||
|
||||
const queryChartsAggList = async () => {
|
||||
loading.value = true;
|
||||
const resp = await getPropertiesInfo(instanceStore.current.id, {
|
||||
columns: [
|
||||
{
|
||||
property: prop.data.id,
|
||||
alias: prop.data.id,
|
||||
agg: agg.value,
|
||||
},
|
||||
],
|
||||
query: {
|
||||
interval: cycle.value,
|
||||
format: 'yyyy-MM-dd HH:mm:ss',
|
||||
from: prop.time[0],
|
||||
to: prop.time[1],
|
||||
},
|
||||
});
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
const dataList: any[] = [
|
||||
{
|
||||
year: prop.time[1],
|
||||
value: undefined,
|
||||
type: prop.data?.name || '',
|
||||
},
|
||||
];
|
||||
(resp.result as any[]).forEach((i: any) => {
|
||||
dataList.push({
|
||||
...i,
|
||||
year: i.time,
|
||||
value: Number(i[prop.data.id || '']),
|
||||
type: prop.data?.name || '',
|
||||
});
|
||||
});
|
||||
dataList.push({
|
||||
year: prop.time[0],
|
||||
value: undefined,
|
||||
type: prop.data?.name || '',
|
||||
});
|
||||
chartsList.value = (dataList || []).reverse();
|
||||
}
|
||||
};
|
||||
|
||||
const queryChartsList = async () => {
|
||||
loading.value = true;
|
||||
const resp = await getPropertiesList(
|
||||
instanceStore.current.id,
|
||||
prop.data.id,
|
||||
{
|
||||
paging: false,
|
||||
terms: [
|
||||
{
|
||||
column: 'timestamp$BTW',
|
||||
value:
|
||||
prop.time[0] && prop.time[1]
|
||||
? [prop.time[0], prop.time[1]]
|
||||
: [],
|
||||
type: 'and',
|
||||
},
|
||||
],
|
||||
sorts: [{ name: 'timestamp', order: 'asc' }],
|
||||
},
|
||||
);
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
const dataList: any[] = [
|
||||
{
|
||||
year: prop.time[0],
|
||||
value: undefined,
|
||||
type: prop.data?.name || '',
|
||||
},
|
||||
];
|
||||
(resp.result as any)?.data?.forEach((i: any) => {
|
||||
dataList.push({
|
||||
...i,
|
||||
year: i.timestamp,
|
||||
value: i.value,
|
||||
type: prop.data?.name || '',
|
||||
});
|
||||
});
|
||||
dataList.push({
|
||||
year: prop.time[1],
|
||||
value: undefined,
|
||||
type: prop.data?.name || '',
|
||||
});
|
||||
chartsList.value = dataList || [];
|
||||
}
|
||||
};
|
||||
|
||||
const getOptions = (arr: any[]) => {
|
||||
options.value = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: arr.map((item) => {
|
||||
return echarts.format.formatTime(
|
||||
'yyyy-MM-dd\nhh:mm:ss',
|
||||
item.year,
|
||||
false,
|
||||
);
|
||||
}),
|
||||
name: '时间',
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: arr[0]?.type,
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
start: 0,
|
||||
end: 10,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 10,
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
position: function (pt: any) {
|
||||
return [pt[0], '10%'];
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: arr.map((i: any) => i.value),
|
||||
type: 'line',
|
||||
areaStyle: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
watch(
|
||||
() => [cycle, agg],
|
||||
([newCycle, newAgg]) => {
|
||||
if (newCycle.value === '*' && _type.value) {
|
||||
queryChartsList();
|
||||
} else {
|
||||
queryChartsAggList();
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
watchEffect(() => {
|
||||
if (chartsList.value.length) {
|
||||
getOptions(chartsList.value);
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<div style="position: relative">
|
||||
<div style="position: absolute; right: 0; top: 5px; z-index: 999">
|
||||
<a-space>
|
||||
<a-button type="primary">开始动画</a-button>
|
||||
<a-button type="primary">停止动画</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<AMap :points="geoList" />
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getPropertyData } from '@/api/device/instance';
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import encodeQuery from '@/utils/encodeQuery';
|
||||
import AMap from './AMap.vue';
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
|
||||
const prop = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
time: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const geoList = ref<any[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const query = async () => {
|
||||
loading.value = true;
|
||||
const resp = await getPropertyData(
|
||||
instanceStore.current.id,
|
||||
encodeQuery({
|
||||
paging: false,
|
||||
terms: {
|
||||
property: prop.data.id,
|
||||
timestamp$BTW: prop.time[0] && prop.time[1] ? prop.time : [],
|
||||
},
|
||||
sorts: { timestamp: 'asc' },
|
||||
}),
|
||||
);
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
const list: any[] = [];
|
||||
((resp.result as any)?.data || []).forEach((item: any) => {
|
||||
list.push([item.value.lon, item.value.lat]);
|
||||
});
|
||||
geoList.value = list
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => [prop.data.id, prop.time],
|
||||
([newVal]) => {
|
||||
if (newVal) {
|
||||
query();
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true, immediate: true
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,194 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
size="small"
|
||||
rowKey="id"
|
||||
:dataSource="dataSource?.data"
|
||||
@change="onChange"
|
||||
:pagination="{
|
||||
current: (dataSource?.pageIndex || 0) + 1,
|
||||
pageSize: dataSource?.pageSize || 10,
|
||||
showSizeChanger: true,
|
||||
total: dataSource?.total || 0,
|
||||
pageSizeOptions: [5, 10, 20, 50],
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'timestamp'">
|
||||
{{ moment(record.timestamp).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</template>
|
||||
<template v-if="column.key === 'value'">
|
||||
<ValueRender
|
||||
type="table"
|
||||
:data="_props.data"
|
||||
:value="{ formatValue: record.value }"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button
|
||||
v-if="
|
||||
showLoad ||
|
||||
(!getType(record?.value) &&
|
||||
data?.valueType?.fileType === 'base64')
|
||||
"
|
||||
type="link"
|
||||
@click="_download(record)"
|
||||
><AIcon type="DownloadOutlined"
|
||||
/></a-button>
|
||||
<a-button type="link" @click="showDetail(record)"
|
||||
><AIcon type="SearchOutlined"
|
||||
/></a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<a-modal
|
||||
title="详情"
|
||||
:visible="visible"
|
||||
@ok="visible = false"
|
||||
@cancel="visible = false"
|
||||
>
|
||||
<div>自定义属性</div>
|
||||
<JsonViewer
|
||||
v-if="
|
||||
data?.valueType?.type === 'object' ||
|
||||
data?.valueType?.type === 'array'
|
||||
"
|
||||
:expand-depth="5"
|
||||
:value="current.formatValue"
|
||||
/>
|
||||
<a-textarea
|
||||
v-else-if="data?.valueType?.type === 'file'"
|
||||
:value="current.formatValue"
|
||||
:row="3"
|
||||
/>
|
||||
<a-input v-else disabled :value="current.formatValue" />
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getPropertyData } from '@/api/device/instance';
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import encodeQuery from '@/utils/encodeQuery';
|
||||
import moment from 'moment';
|
||||
import { getType } from '../index';
|
||||
import ValueRender from '../ValueRender.vue';
|
||||
import JsonViewer from 'vue-json-viewer';
|
||||
|
||||
const _props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
time: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
const dataSource = ref({});
|
||||
const current = ref<any>({});
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
const columns = computed(() => {
|
||||
const arr: any[] = [
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: _props.data?.name || '',
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
ellipsis: true,
|
||||
},
|
||||
];
|
||||
if (_props.data?.valueType?.type != 'geoPoint') {
|
||||
arr.push({
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
});
|
||||
}
|
||||
return arr;
|
||||
});
|
||||
|
||||
const showLoad = computed(() => {
|
||||
return (
|
||||
_props.data.valueType?.type === 'file' &&
|
||||
_props.data?.valueType?.fileType === 'Binary(二进制)'
|
||||
);
|
||||
});
|
||||
|
||||
const showDetail = (item: any) => {
|
||||
visible.value = true;
|
||||
current.value = item;
|
||||
};
|
||||
|
||||
const queryPropertyData = async (params: any) => {
|
||||
const resp = await getPropertyData(
|
||||
instanceStore.current.id,
|
||||
encodeQuery({
|
||||
...params,
|
||||
terms: {
|
||||
property: _props.data.id,
|
||||
timestamp$BTW: _props.time,
|
||||
},
|
||||
sorts: { timestamp: 'desc' },
|
||||
}),
|
||||
);
|
||||
if (resp.status === 200) {
|
||||
dataSource.value = resp.result as any;
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => [_props.data.id, _props.time],
|
||||
([newVal]) => {
|
||||
if (newVal) {
|
||||
queryPropertyData({
|
||||
pageSize: _props.data.valueType?.type === 'file' ? 5 : 10,
|
||||
pageIndex: 0,
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true, immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
const onChange = (page: any) => {
|
||||
queryPropertyData({
|
||||
pageSize: page.pageSize,
|
||||
pageIndex: Number(page.current) - 1 || 0,
|
||||
});
|
||||
};
|
||||
|
||||
const _download = (record: any) => {
|
||||
const downNode = document.createElement('a');
|
||||
downNode.download = `${instanceStore.current.name}-${
|
||||
_props.data.name
|
||||
}${moment(new Date().getTime()).format('YYYY-MM-DD-HH-mm-ss')}.txt`;
|
||||
downNode.style.display = 'none';
|
||||
//字符串内容转成Blob地址
|
||||
const blob = new Blob([record.value]);
|
||||
downNode.href = URL.createObjectURL(blob);
|
||||
//触发点击
|
||||
document.body.appendChild(downNode);
|
||||
downNode.click();
|
||||
//移除
|
||||
document.body.removeChild(downNode);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-pagination-item) {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,81 @@
|
|||
<template>
|
||||
<a-space>
|
||||
<a-radio-group
|
||||
:value="radioValue"
|
||||
button-style="solid"
|
||||
@change="onRadioChange"
|
||||
>
|
||||
<a-radio-button value="today">今日</a-radio-button>
|
||||
<a-radio-button value="week">近一周</a-radio-button>
|
||||
<a-radio-button value="month">近一月</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-range-picker
|
||||
show-time
|
||||
v-model:value="dateValue"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
@change="onRangeChange"
|
||||
:allowClear="false"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import dayjs from 'dayjs';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
type Props = [Dayjs, Dayjs] | undefined
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object as PropType<Props>,
|
||||
default: undefined
|
||||
},
|
||||
});
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:modelValue', data: Props): void;
|
||||
};
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const radioValue = ref<string>('today');
|
||||
const dateValue = ref<Props>();
|
||||
|
||||
const onRangeChange = (value: Props) => {
|
||||
emit('update:modelValue', value);
|
||||
radioValue.value = '';
|
||||
}
|
||||
|
||||
const getTime = (type: string): Props => {
|
||||
let st: number = 0;
|
||||
const et = new Date().getTime();
|
||||
if (type === 'today') {
|
||||
st = dayjs().startOf('day').valueOf();
|
||||
} else if (type === 'week') {
|
||||
st = dayjs().subtract(6, 'days').valueOf();
|
||||
} else if (type === 'month') {
|
||||
st = dayjs().subtract(29, 'days').valueOf();
|
||||
}
|
||||
return [dayjs(st), dayjs(et)]
|
||||
}
|
||||
|
||||
const onRadioChange = (e: any) => {
|
||||
const value: string = e.target.value;
|
||||
radioValue.value = value;
|
||||
emit('update:modelValue', getTime(value));
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
radioValue.value = 'today'
|
||||
emit('update:modelValue', getTime('today'));
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal: Props) => {
|
||||
dateValue.value = newVal
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
</script>
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<a-modal title="详情" visible width="50vw" @ok="onCancel" @cancel="onCancel">
|
||||
<div style="margin-bottom: 10px"><TimeComponent v-model="dateValue" /></div>
|
||||
<div>
|
||||
<a-tabs v-model:activeKey="activeKey" style="max-height: 600px; overflow-y: auto">
|
||||
<a-tab-pane key="table" tab="列表">
|
||||
<Table :data="props.data" :time="_getTimes" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="charts" tab="图表">
|
||||
<Charts :data="props.data" :time="_getTimes" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="geo" tab="轨迹" v-if="data?.valueType?.type === 'geoPoint'">
|
||||
<PropertyAMap :data="props.data" :time="_getTimes" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import TimeComponent from './TimeComponent.vue'
|
||||
import Charts from './Charts.vue'
|
||||
import PropertyAMap from './PropertyAMap.vue'
|
||||
import Table from './Table.vue'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
|
||||
const _emits = defineEmits(['close'])
|
||||
|
||||
const activeKey = ref<'table' | 'charts' | 'geo'>('table')
|
||||
|
||||
const dateValue = ref<[Dayjs, Dayjs]>();
|
||||
|
||||
const _getTimes = computed(() => {
|
||||
if(dateValue.value){
|
||||
return [dateValue.value[0].valueOf(), dateValue.value[1].valueOf()]
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
const onCancel = () => {
|
||||
_emits('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
<template>
|
||||
<a-card :hoverable="true" class="card-box">
|
||||
<!-- <a-spin :spinning="loading"> -->
|
||||
<div class="card-container">
|
||||
<div class="header">
|
||||
<div class="title">{{ _props.data.name }}</div>
|
||||
<div class="extra">
|
||||
<a-space :size="16">
|
||||
<div class="card-container">
|
||||
<div class="header">
|
||||
<div class="title">{{ _props.data.name }}</div>
|
||||
<div class="extra">
|
||||
<a-space :size="16">
|
||||
<template v-for="i in actions" :key="i.key">
|
||||
<a-tooltip
|
||||
v-for="i in actions"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
v-if="i.key !== 'edit'"
|
||||
>
|
||||
<a-button
|
||||
style="padding: 0; margin: 0"
|
||||
|
@ -17,26 +17,48 @@
|
|||
:disabled="i.disabled"
|
||||
@click="i.onClick && i.onClick(data)"
|
||||
>
|
||||
<AIcon :type="i.icon" style="color: #323130; font-size: 12px" />
|
||||
<AIcon
|
||||
:type="i.icon"
|
||||
style="color: #323130; font-size: 12px"
|
||||
/>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<div class="value">
|
||||
<ValueRender :data="data" :value="_props.data" type="card" />
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div style="color: rgba(0, 0, 0, .65); font-size: 12px">更新时间</div>
|
||||
<div class="time-value">{{_props?.data?.timeString || '--'}}</div>
|
||||
<PermissionButton
|
||||
:disabled="i.disabled"
|
||||
v-else
|
||||
:popConfirm="i.popConfirm"
|
||||
:tooltip="i.tooltip"
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
type="link"
|
||||
style="padding: 0px"
|
||||
:hasPermission="'device/Instance:update'"
|
||||
>
|
||||
<template #icon
|
||||
><AIcon :type="i.icon" style="color: #323130; font-size: 12px"
|
||||
/></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<div class="value">
|
||||
<ValueRender :data="data" :value="_props.data" type="card" />
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div style="color: rgba(0, 0, 0, 0.65); font-size: 12px">
|
||||
更新时间
|
||||
</div>
|
||||
<div class="time-value">
|
||||
{{ _props?.data?.timeString || '--' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- </a-spin> -->
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ValueRender from './ValueRender.vue'
|
||||
import ValueRender from './ValueRender.vue';
|
||||
const _props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
|
@ -44,7 +66,7 @@ const _props = defineProps({
|
|||
},
|
||||
actions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
// const loading = ref<boolean>(true);
|
||||
|
@ -101,6 +123,6 @@ const _props = defineProps({
|
|||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -13,23 +13,18 @@
|
|||
<a-image :src="value?.formatValue" />
|
||||
</template>
|
||||
<template v-else-if="['.flv', '.m3u8', '.mp4'].includes(type)">
|
||||
<!-- TODO 视频组件缺失 -->
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- <json-viewer
|
||||
:value="{
|
||||
'id': '123'
|
||||
}"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
></json-viewer> -->
|
||||
<JsonViewer
|
||||
:expand-depth="5"
|
||||
:value="value?.formatValue"
|
||||
/>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// import JsonViewer from 'vue3-json-viewer';
|
||||
import JsonViewer from 'vue-json-viewer';
|
||||
|
||||
const _data = defineProps({
|
||||
type: {
|
||||
|
@ -46,9 +41,6 @@ const handleCancel = () => {
|
|||
_emit('close');
|
||||
};
|
||||
|
||||
// watchEffect(() => {
|
||||
// console.log(_data.value?.formatValue)
|
||||
// })
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="value">
|
||||
<div v-if="value?.formatValue !== 0 && !value?.formatValue" :class="valueClass">--</div>
|
||||
<div v-else-if="data?.valueType?.type === 'file'">
|
||||
<div v-else-if="_data.data?.valueType?.type === 'file'">
|
||||
<template v-if="data?.valueType?.fileType === 'base64'">
|
||||
<div :class="valueClass" v-if="!!getType(value?.formatValue)">
|
||||
<img :src="imgMap.get(_type)" @error="onError" />
|
||||
|
@ -36,10 +36,10 @@
|
|||
</template>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else-if="data?.valueType?.type === 'object'" @click="getDetail('obj')" :class="valueClass">
|
||||
<div v-else-if="_data.data?.valueType?.type === 'object'" @click="getDetail('obj')" :class="valueClass">
|
||||
<img :src="imgMap.get('obj')" />
|
||||
</div>
|
||||
<div v-else-if="data?.valueType?.type === 'geoPoint' || data?.valueType?.type === 'array'" :class="valueClass">
|
||||
<div v-else-if="_data.data?.valueType?.type === 'geoPoint' || _data.data?.valueType?.type === 'array'" :class="valueClass">
|
||||
{{JSON.stringify(value?.formatValue)}}
|
||||
</div>
|
||||
<div v-else :class="valueClass">
|
||||
|
@ -53,6 +53,7 @@
|
|||
import { getImage } from "@/utils/comm";
|
||||
import { message } from "ant-design-vue";
|
||||
import ValueDetail from './ValueDetail.vue'
|
||||
import {getType, imgMap, imgList, videoList, fileList} from './index'
|
||||
|
||||
const _data = defineProps({
|
||||
data: {
|
||||
|
@ -73,47 +74,12 @@ const valueClass = computed(() => {
|
|||
return _data.type === 'card' ? 'cardValue' : 'otherValue'
|
||||
})
|
||||
|
||||
const imgMap = new Map<any, any>();
|
||||
imgMap.set('txt', getImage('/running/txt.png'));
|
||||
imgMap.set('doc', getImage('/running/doc.png'));
|
||||
imgMap.set('xls', getImage('/running/xls.png'));
|
||||
imgMap.set('ppt', getImage('/running/ppt.png'));
|
||||
imgMap.set('docx', getImage('/running/docx.png'));
|
||||
imgMap.set('xlsx', getImage('/running/xlsx.png'));
|
||||
imgMap.set('pptx', getImage('/running/pptx.png'));
|
||||
imgMap.set('pdf', getImage('/running/pdf.png'));
|
||||
imgMap.set('img', getImage('/running/img.png'));
|
||||
imgMap.set('error', getImage('/running/error.png'));
|
||||
imgMap.set('video', getImage('/running/video.png'));
|
||||
imgMap.set('other', getImage('/running/other.png'));
|
||||
imgMap.set('obj', getImage('/running/obj.png'));
|
||||
|
||||
const imgList = ['.jpg', '.png', '.swf', '.tiff'];
|
||||
const videoList = ['.m3u8', '.flv', '.mp4', '.rmvb', '.mvb'];
|
||||
const fileList = ['.txt', '.doc', '.xls', '.pdf', '.ppt', '.docx', '.xlsx', '.pptx'];
|
||||
|
||||
const isHttps = document.location.protocol === 'https:';
|
||||
|
||||
const _types = ref<string>('')
|
||||
const visible = ref<boolean>(false)
|
||||
const temp = ref<boolean>(false)
|
||||
|
||||
const getType = (url: string) => {
|
||||
let t: string = '';
|
||||
[...imgList, ...videoList, ...fileList].map((item) => {
|
||||
const str = item.slice(1, item.length);
|
||||
if (url && String(url).indexOf(str) !== -1) {
|
||||
if (imgList.includes(item)) {
|
||||
t = 'img';
|
||||
} else if (videoList.includes(item)) {
|
||||
t = 'video';
|
||||
} else {
|
||||
t = str;
|
||||
}
|
||||
}
|
||||
});
|
||||
return t;
|
||||
};
|
||||
|
||||
const onError = (e: any) => {
|
||||
e.target.src = imgMap.get('other')
|
||||
|
@ -149,7 +115,6 @@ const getDetail = (_type: string) => {
|
|||
_types.value = flag
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import { getImage } from "@/utils/comm";
|
||||
|
||||
export const imgMap = new Map<any, any>();
|
||||
imgMap.set('txt', getImage('/running/txt.png'));
|
||||
imgMap.set('doc', getImage('/running/doc.png'));
|
||||
imgMap.set('xls', getImage('/running/xls.png'));
|
||||
imgMap.set('ppt', getImage('/running/ppt.png'));
|
||||
imgMap.set('docx', getImage('/running/docx.png'));
|
||||
imgMap.set('xlsx', getImage('/running/xlsx.png'));
|
||||
imgMap.set('pptx', getImage('/running/pptx.png'));
|
||||
imgMap.set('pdf', getImage('/running/pdf.png'));
|
||||
imgMap.set('img', getImage('/running/img.png'));
|
||||
imgMap.set('error', getImage('/running/error.png'));
|
||||
imgMap.set('video', getImage('/running/video.png'));
|
||||
imgMap.set('other', getImage('/running/other.png'));
|
||||
imgMap.set('obj', getImage('/running/obj.png'));
|
||||
|
||||
export const imgList = ['.jpg', '.png', '.swf', '.tiff'];
|
||||
export const videoList = ['.m3u8', '.flv', '.mp4', '.rmvb', '.mvb'];
|
||||
export const fileList = ['.txt', '.doc', '.xls', '.pdf', '.ppt', '.docx', '.xlsx', '.pptx'];
|
||||
|
||||
export const getType = (url: string) => {
|
||||
let t: string = '';
|
||||
[...imgList, ...videoList, ...fileList].map((item) => {
|
||||
const str = item.slice(1, item.length);
|
||||
if (url && String(url).indexOf(str) !== -1) {
|
||||
if (imgList.includes(item)) {
|
||||
t = 'img';
|
||||
} else if (videoList.includes(item)) {
|
||||
t = 'video';
|
||||
} else {
|
||||
t = str;
|
||||
}
|
||||
}
|
||||
});
|
||||
return t;
|
||||
};
|
|
@ -32,20 +32,30 @@
|
|||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps)"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
<template v-for="i in getActions(slotProps)" :key="i.key">
|
||||
<a-tooltip v-bind="i.tooltip" v-if="i.key !== 'edit'">
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
:disabled="i.disabled"
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
>
|
||||
<AIcon :type="i.icon" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<PermissionButton
|
||||
:disabled="i.disabled"
|
||||
v-else
|
||||
:popConfirm="i.popConfirm"
|
||||
:tooltip="i.tooltip"
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
type="link"
|
||||
style="padding: 0px"
|
||||
:hasPermission="'device/Instance:update'"
|
||||
>
|
||||
<AIcon :type="i.icon" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #paginationRender>
|
||||
|
@ -76,14 +86,20 @@
|
|||
@close="indicatorVisible = false"
|
||||
:data="currentInfo"
|
||||
/>
|
||||
<Detail
|
||||
v-if="detailVisible"
|
||||
:data="currentInfo"
|
||||
@close="detailVisible = false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import _, { groupBy, throttle, toArray } from 'lodash-es';
|
||||
import _, { groupBy, toArray } from 'lodash-es';
|
||||
import { PropertyData } from '../../../typings';
|
||||
import PropertyCard from './PropertyCard.vue';
|
||||
import ValueRender from './ValueRender.vue';
|
||||
import Save from './Save.vue';
|
||||
import Detail from './Detail/index.vue';
|
||||
import Indicators from './Indicators.vue';
|
||||
import { getProperty } from '@/api/device/instance';
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
|
@ -238,11 +254,15 @@ const subscribeProperty = () => {
|
|||
?.pipe(map((res: any) => res.payload))
|
||||
.subscribe((payload) => {
|
||||
list.value = [...list.value, payload];
|
||||
unref(list).sort((a: any, b: any) => a.timestamp - b.timestamp)
|
||||
.forEach((item: any) => {
|
||||
const { value } = item;
|
||||
propertyValue.value[value?.property] = { ...item, ...value };
|
||||
});
|
||||
unref(list)
|
||||
.sort((a: any, b: any) => a.timestamp - b.timestamp)
|
||||
.forEach((item: any) => {
|
||||
const { value } = item;
|
||||
propertyValue.value[value?.property] = {
|
||||
...item,
|
||||
...value,
|
||||
};
|
||||
});
|
||||
// list.value = [...list.value, payload];
|
||||
// throttle(valueChange(list.value), 500);
|
||||
});
|
||||
|
@ -335,8 +355,8 @@ const onSearch = () => {
|
|||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
subRef.value && subRef.value?.unsubscribe()
|
||||
})
|
||||
subRef.value && subRef.value?.unsubscribe();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<a-tabs
|
||||
tab-position="left"
|
||||
style="height: 600px"
|
||||
v-if="tabList.length"
|
||||
v-model:activeKey="activeKey"
|
||||
:tabBarStyle="{ width: '200px' }"
|
||||
@change="tabChange"
|
||||
|
@ -22,6 +23,7 @@
|
|||
:tab="i.tab"
|
||||
/>
|
||||
</a-tabs>
|
||||
<JEmpty v-else style="margin: 250px 0" />
|
||||
</div>
|
||||
<div class="property-box-right">
|
||||
<Event v-if="type === 'event'" :data="data" />
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<page-container
|
||||
:tabList="list"
|
||||
@back="onBack"
|
||||
:tabActiveKey="instanceStore.active"
|
||||
:tabActiveKey="instanceStore.tabActiveKey"
|
||||
@tabChange="onTabChange"
|
||||
>
|
||||
<template #title>
|
||||
|
|
|
@ -61,14 +61,15 @@
|
|||
showSearch
|
||||
v-model:value="modelRef.productId"
|
||||
placeholder="请选择所属产品"
|
||||
:filter-option="filterOption"
|
||||
>
|
||||
<a-select-option
|
||||
:value="item.id"
|
||||
v-for="item in productList"
|
||||
:key="item.id"
|
||||
:title="item.name"
|
||||
:label="item.name"
|
||||
:disabled="!!props.data.id"
|
||||
></a-select-option>
|
||||
>{{item.name}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="说明" name="describe">
|
||||
|
@ -110,6 +111,10 @@ const modelRef = reactive({
|
|||
photoUrl: getImage('/device/instance/device-card.png'),
|
||||
});
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
const vailId = async (_: Record<string, any>, value: string) => {
|
||||
if (!props?.data?.id && value) {
|
||||
const resp = await isExists(value);
|
||||
|
|
|
@ -140,7 +140,6 @@
|
|||
:value="slotProps"
|
||||
@click="handleClick"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
v-bind="slotProps"
|
||||
:active="_selectedRowKeys.includes(slotProps.id)"
|
||||
:status="slotProps.state?.value"
|
||||
:statusText="slotProps.state?.text"
|
||||
|
@ -151,22 +150,17 @@
|
|||
}"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img
|
||||
:src="
|
||||
getImage('/device/instance/device-card.png')
|
||||
"
|
||||
/>
|
||||
</slot>
|
||||
<img
|
||||
:src="getImage('/device/instance/device-card.png')"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3
|
||||
class="card-item-content-title"
|
||||
@click.stop="handleView(slotProps.id)"
|
||||
>
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
<a-row>
|
||||
<Ellipsis style="width: calc(100% - 100px)">
|
||||
<span style="font-size: 16px; font-weight: 600" @click.stop="handleView(slotProps.id)">
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<a-row style="margin-top: 20px">
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
|
@ -177,7 +171,9 @@
|
|||
<div class="card-item-content-text">
|
||||
产品名称
|
||||
</div>
|
||||
<div>{{ slotProps.productName }}</div>
|
||||
<Ellipsis style="width: 100%">
|
||||
{{ slotProps.productName }}
|
||||
</Ellipsis>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
@ -292,7 +288,7 @@ const operationVisible = ref<boolean>(false);
|
|||
const api = ref<string>('');
|
||||
const type = ref<string>('');
|
||||
|
||||
const menuStory = useMenuStore()
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('online', 'success');
|
||||
|
@ -538,7 +534,7 @@ const handleAdd = () => {
|
|||
* 查看
|
||||
*/
|
||||
const handleView = (id: string) => {
|
||||
menuStory.jumpPage('device/Instance/Detail', {id})
|
||||
menuStory.jumpPage('device/Instance/Detail', { id });
|
||||
};
|
||||
|
||||
const getActions = (
|
||||
|
|
|
@ -1,169 +1,180 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<a-card class="device-product">
|
||||
<Search
|
||||
:columns="query.columns"
|
||||
target="product-manage"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JTable
|
||||
:columns="columns"
|
||||
:request="queryProductList"
|
||||
ref="tableRef"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
}"
|
||||
:params="params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="add"
|
||||
><plus-outlined />新增</a-button
|
||||
>
|
||||
<a-upload
|
||||
name="file"
|
||||
accept=".json"
|
||||
:showUploadList="false"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<a-button>导入</a-button>
|
||||
</a-upload>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #deviceType="slotProps">
|
||||
<div>{{ slotProps.deviceType.text }}</div>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value="slotProps"
|
||||
@click="handleClick"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
v-bind="slotProps"
|
||||
:active="_selectedRowKeys.includes(slotProps.id)"
|
||||
:status="slotProps.state"
|
||||
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
||||
:statusNames="{
|
||||
1: 'success',
|
||||
0: 'error',
|
||||
}"
|
||||
<Search
|
||||
:columns="query.columns"
|
||||
target="product-manage"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JTable
|
||||
:columns="columns"
|
||||
:request="queryProductList"
|
||||
ref="tableRef"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
}"
|
||||
:params="params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="add"
|
||||
><plus-outlined />新增</a-button
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img :src="getImage('/device-product.png')" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3
|
||||
<a-upload
|
||||
name="file"
|
||||
accept=".json"
|
||||
:showUploadList="false"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<a-button>导入</a-button>
|
||||
</a-upload>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #deviceType="slotProps">
|
||||
<div>{{ slotProps.deviceType.text }}</div>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value="slotProps"
|
||||
@click="handleClick"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
v-bind="slotProps"
|
||||
:active="_selectedRowKeys.includes(slotProps.id)"
|
||||
:status="slotProps.state"
|
||||
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
||||
:statusNames="{
|
||||
1: 'success',
|
||||
0: 'error',
|
||||
}"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img
|
||||
:src="
|
||||
slotProps.photoUrl ||
|
||||
getImage('/device-product.png')
|
||||
"
|
||||
class="productImg"
|
||||
/>
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis
|
||||
><span
|
||||
@click.stop="handleView(slotProps.id)"
|
||||
style="font-weight: 600"
|
||||
style="font-weight: 600; font-size: 16px"
|
||||
>
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
</div>
|
||||
<div>直连设备</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<a-tooltip
|
||||
v-bind="item.tooltip"
|
||||
:title="item.disabled && item.tooltip.title"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="item.popConfirm"
|
||||
v-bind="item.popConfirm"
|
||||
:disabled="item.disabled"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
</span></Ellipsis
|
||||
>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
</div>
|
||||
<div>{{ slotProps?.deviceType?.text }}</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
接入方式
|
||||
</div>
|
||||
<Ellipsis
|
||||
><div>
|
||||
{{ slotProps?.accessName }}
|
||||
</div></Ellipsis
|
||||
>
|
||||
<a-button :disabled="item.disabled">
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<template v-else>
|
||||
<a-button
|
||||
:disabled="item.disabled"
|
||||
@click="item.onClick"
|
||||
>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
:text="slotProps.state === 1 ? '正常' : '禁用'"
|
||||
:status="statusMap.get(slotProps.state)"
|
||||
/>
|
||||
</template>
|
||||
<template #id="slotProps">
|
||||
<a>{{ slotProps.id }}</a>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps)"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
v-bind="item.tooltip"
|
||||
:title="item.disabled && item.tooltip.title"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
v-if="item.popConfirm"
|
||||
v-bind="item.popConfirm"
|
||||
:disabled="item.disabled"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
<a-button :disabled="item.disabled">
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<template v-else>
|
||||
<a-button
|
||||
:disabled="item.disabled"
|
||||
@click="item.onClick"
|
||||
>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
:text="slotProps.state === 1 ? '正常' : '禁用'"
|
||||
:status="statusMap.get(slotProps.state)"
|
||||
/>
|
||||
</template>
|
||||
<template #id="slotProps">
|
||||
<a>{{ slotProps.id }}</a>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps)"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
<!-- 新增、编辑 -->
|
||||
<Save
|
||||
ref="saveRef"
|
||||
:isAdd="isAdd"
|
||||
:title="title"
|
||||
@success="refresh"
|
||||
/>
|
||||
</a-card>
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
<!-- 新增、编辑 -->
|
||||
<Save ref="saveRef" :isAdd="isAdd" :title="title" @success="refresh" />
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
|
@ -195,11 +206,11 @@ import { isNoCommunity, downloadObject } from '@/utils/utils';
|
|||
import { omit } from 'lodash-es';
|
||||
import { typeOptions } from '@/components/Search/util';
|
||||
import Save from './Save/index.vue';
|
||||
import { useMenuStore } from 'store/menu'
|
||||
import { useMenuStore } from 'store/menu';
|
||||
/**
|
||||
* 表格数据
|
||||
*/
|
||||
const menuStory = useMenuStore()
|
||||
const menuStory = useMenuStore();
|
||||
const router = useRouter();
|
||||
const isAdd = ref<number>(0);
|
||||
const title = ref<string>('');
|
||||
|
@ -427,7 +438,7 @@ const beforeUpload = (file: any) => {
|
|||
* 查看
|
||||
*/
|
||||
const handleView = (id: string) => {
|
||||
menuStory.jumpPage('device/Product/Detail',{id})
|
||||
menuStory.jumpPage('device/Product/Detail', { id });
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -645,4 +656,13 @@ const handleSearch = (e: any) => {
|
|||
padding: 20px;
|
||||
background: #f0f2f5;
|
||||
}
|
||||
.productImg {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
}
|
||||
.productName {
|
||||
white-space: nowrap; /*强制在同一行内显示所有文本,直到文本结束或者遭遇br标签对象才换行。*/
|
||||
overflow: hidden; /*超出部分隐藏*/
|
||||
text-overflow: ellipsis; /*隐藏部分以省略号代替*/
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
]">
|
||||
<a-select v-model:value="_value.source" :options="PropertySource" size="small" :disabled="metadataStore.model.action === 'edit'"></a-select>
|
||||
</a-form-item>
|
||||
<virtual-rule-param v-if="_value.source === 'rule'" v-model:value="_value.virtualRule" :name="name.concat(['virtualRule'])" :id="id"></virtual-rule-param>
|
||||
<virtual-rule-param v-if="_value.source === 'rule'" v-model:value="_value.virtualRule" :name="name.concat(['virtualRule'])" :id="id" :showWindow="_value.source === 'rule'"></virtual-rule-param>
|
||||
<a-form-item label="读写类型" :name="name.concat(['type'])" :rules="[
|
||||
{ required: true, message: '请选择读写类型' },
|
||||
]">
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
>
|
||||
<template #headerTitle>
|
||||
<a-button type="primary" @click="handlAdd"
|
||||
><plus-outlined />新增</a-button
|
||||
><AIcon type="PlusOutlined" />新增</a-button
|
||||
>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
>
|
||||
<template #headerTitle>
|
||||
<a-button type="primary" @click="handlAdd"
|
||||
><plus-outlined />新增</a-button
|
||||
><AIcon type="PlusOutlined" />新增</a-button
|
||||
>
|
||||
</template>
|
||||
<template #type="slotProps">
|
||||
|
@ -72,6 +72,9 @@ const columns = [
|
|||
title: '证书标准',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
fixed: 'left',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
|
@ -87,6 +90,7 @@ const columns = [
|
|||
title: '证书名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
|
@ -95,6 +99,7 @@ const columns = [
|
|||
title: '说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
>
|
||||
<template #headerTitle>
|
||||
<a-button type="primary" @click="handlAdd"
|
||||
><plus-outlined />新增</a-button
|
||||
><AIcon type="PlusOutlined" />新增</a-button
|
||||
>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
>
|
||||
<template #headerTitle>
|
||||
<a-button type="primary" @click="handlAdd"
|
||||
><plus-outlined />新增</a-button
|
||||
><AIcon type="PlusOutlined" />新增</a-button
|
||||
>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Search
|
||||
type="simple"
|
||||
:columns="columns"
|
||||
target="product"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
|
||||
<JTable
|
||||
ref="instanceRef"
|
||||
:columns="columns"
|
||||
:request="(e:any) => templateApi.getHistory(e, data.id)"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'notifyTime', order: 'desc' }],
|
||||
terms: [{ column: 'notifyType$IN', value: data.type }],
|
||||
}"
|
||||
:params="params"
|
||||
model="table"
|
||||
>
|
||||
<template #notifyTime="slotProps">
|
||||
{{ moment(slotProps.notifyTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-space>
|
||||
<a-badge
|
||||
:status="slotProps.state.value"
|
||||
:text="slotProps.state.text"
|
||||
></a-badge>
|
||||
<AIcon
|
||||
v-if="slotProps.state.value === 'error'"
|
||||
type="ExclamationCircleOutlined"
|
||||
style="color: #1d39c4; cursor: pointer"
|
||||
@click="handleError(slotProps.errorStack)"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<AIcon
|
||||
type="ExclamationCircleOutlined"
|
||||
style="color: #1d39c4; cursor: pointer"
|
||||
@click="handleDetail(slotProps.context)"
|
||||
/>
|
||||
</template>
|
||||
</JTable>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import templateApi from '@/api/notice/template';
|
||||
import { PropType } from 'vue';
|
||||
import moment from 'moment';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:visible', data: boolean): void;
|
||||
};
|
||||
// const emit = defineEmits<Emits>();
|
||||
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean, default: false },
|
||||
data: {
|
||||
type: Object as PropType<Partial<Record<string, any>>>,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
// const _vis = computed({
|
||||
// get: () => props.visible,
|
||||
// set: (val) => emit('update:visible', val),
|
||||
// });
|
||||
|
||||
// watch(
|
||||
// () => _vis.value,
|
||||
// (val) => {
|
||||
// if (val) handleSearch({ terms: [] });
|
||||
// },
|
||||
// );
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '发送时间',
|
||||
dataIndex: 'notifyTime',
|
||||
key: 'notifyTime',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'date',
|
||||
handleValue: (v: any) => {
|
||||
return v;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '成功', value: 'success' },
|
||||
{ label: '失败', value: 'error' },
|
||||
],
|
||||
handleValue: (v: any) => {
|
||||
return v;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
|
||||
const params = ref<Record<string, any>>({});
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* @param params
|
||||
*/
|
||||
const handleSearch = (e: any) => {
|
||||
// console.log('handleSearch e:', e);
|
||||
params.value = e;
|
||||
// console.log('params.value: ', params.value);
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看错误信息
|
||||
*/
|
||||
const handleError = (e: any) => {
|
||||
Modal.info({
|
||||
title: '错误信息',
|
||||
content: h(
|
||||
'p',
|
||||
{
|
||||
style: {
|
||||
maxHeight: '300px',
|
||||
overflowY: 'auto',
|
||||
},
|
||||
},
|
||||
JSON.stringify(e),
|
||||
),
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 查看详情
|
||||
*/
|
||||
const handleDetail = (e: any) => {
|
||||
Modal.info({
|
||||
title: '详情信息',
|
||||
content: h(
|
||||
'p',
|
||||
{
|
||||
style: {
|
||||
maxHeight: '300px',
|
||||
overflowY: 'auto',
|
||||
},
|
||||
},
|
||||
JSON.stringify(e),
|
||||
),
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,114 @@
|
|||
export type CatalogItemType = {
|
||||
district?: string;
|
||||
device?: string;
|
||||
platform?: string;
|
||||
user?: string;
|
||||
platform_outer?: string;
|
||||
ext?: string;
|
||||
};
|
||||
|
||||
export interface CatalogItem {
|
||||
id: string;
|
||||
channelId: string;
|
||||
deviceId: string;
|
||||
name: string;
|
||||
type: CatalogItemType;
|
||||
createTime: number;
|
||||
modifyTime: number;
|
||||
children?: CatalogItem[];
|
||||
}
|
||||
|
||||
export type ChannelStatusType =
|
||||
| 'online'
|
||||
| 'lost'
|
||||
| 'defect'
|
||||
| 'add'
|
||||
| 'delete'
|
||||
| 'update'
|
||||
| 'offline';
|
||||
|
||||
export type PtzType = 'unknown' | 'ball' | 'hemisphere' | 'fixed' | 'remoteControl';
|
||||
|
||||
export type CatalogType = keyof CatalogItemType;
|
||||
|
||||
export type ChannelType =
|
||||
| 'dv_no_storage'
|
||||
| 'dv_has_storage'
|
||||
| 'dv_decoder'
|
||||
| 'networking_monitor_server'
|
||||
| 'media_proxy'
|
||||
| 'web_access_server'
|
||||
| 'video_management_server'
|
||||
| 'network_matrix'
|
||||
| 'network_controller'
|
||||
| 'network_alarm_machine'
|
||||
| 'dvr'
|
||||
| 'video_server'
|
||||
| 'encoder'
|
||||
| 'decoder'
|
||||
| 'video_switching_matrix'
|
||||
| 'audio_switching_matrix'
|
||||
| 'alarm_controller'
|
||||
| 'nvr'
|
||||
| 'hvr'
|
||||
| 'camera'
|
||||
| 'ipc'
|
||||
| 'display'
|
||||
| 'alarm_input'
|
||||
| 'alarm_output'
|
||||
| 'audio_input'
|
||||
| 'audio_output'
|
||||
| 'mobile_trans'
|
||||
| 'other_outer'
|
||||
| 'center_server'
|
||||
| 'web_server'
|
||||
| 'media_dispatcher'
|
||||
| 'proxy_server'
|
||||
| 'secure_server'
|
||||
| 'alarm_server'
|
||||
| 'database_server'
|
||||
| 'gis_server'
|
||||
| 'management_server'
|
||||
| 'gateway_server'
|
||||
| 'media_storage_server'
|
||||
| 'signaling_secure_gateway'
|
||||
| 'business_group'
|
||||
| 'virtual_group'
|
||||
| 'center_user'
|
||||
| 'end_user'
|
||||
| 'media_iap'
|
||||
| 'media_ops'
|
||||
| 'district'
|
||||
| 'other';
|
||||
|
||||
export interface ChannelItem {
|
||||
id: string;
|
||||
deviceId: string;
|
||||
deviceName: string;
|
||||
channelId: string;
|
||||
name: string;
|
||||
manufacturer: string;
|
||||
model: string;
|
||||
address: string;
|
||||
provider: string;
|
||||
status: {
|
||||
value: string;
|
||||
text: string;
|
||||
};
|
||||
others: object;
|
||||
description: string;
|
||||
parentChannelId: string;
|
||||
subCount: integer;
|
||||
civilCode: string;
|
||||
ptzType: PtzType;
|
||||
catalogType: CatalogType;
|
||||
channelType: ChannelType;
|
||||
catalogCode: string;
|
||||
longitude: number;
|
||||
latitude: number;
|
||||
createTime: number;
|
||||
modifyTime: number;
|
||||
parentId: string;
|
||||
gb28181ProxyStream: boolean;
|
||||
gb28181ChannelId: string;
|
||||
}
|
|
@ -51,7 +51,7 @@
|
|||
v-bind="validateInfos.productId"
|
||||
>
|
||||
<a-row :gutter="[0, 10]">
|
||||
<a-col :span="22">
|
||||
<a-col :span="!!route.query.id ? 24 : 22">
|
||||
<a-select
|
||||
v-model:value="formData.productId"
|
||||
placeholder="请选择所属产品"
|
||||
|
@ -66,7 +66,7 @@
|
|||
</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="2">
|
||||
<a-col :span="2" v-if="!route.query.id">
|
||||
<a-button
|
||||
type="link"
|
||||
@click="saveProductVis = true"
|
||||
|
@ -132,12 +132,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>
|
||||
|
@ -356,6 +355,8 @@ const getDetail = async () => {
|
|||
// formData.value = res.result;
|
||||
Object.assign(formData.value, res.result);
|
||||
formData.value.channel = res.result.provider;
|
||||
|
||||
console.log('formData.value: ', formData.value);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -367,6 +368,7 @@ onMounted(() => {
|
|||
*/
|
||||
const btnLoading = ref<boolean>(false);
|
||||
const handleSubmit = () => {
|
||||
// console.log('formData.value: ', formData.value);
|
||||
validate()
|
||||
.then(async () => {
|
||||
btnLoading.value = true;
|
||||
|
|
|
@ -261,9 +261,13 @@ const getActions = (
|
|||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
menuStory.jumpPage('media/Device/Save', {
|
||||
id: data.id,
|
||||
});
|
||||
menuStory.jumpPage(
|
||||
'media/Device/Save',
|
||||
{},
|
||||
{
|
||||
id: data.id,
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -277,10 +281,14 @@ const getActions = (
|
|||
// router.push(
|
||||
// `/media/device/Channel?id=${data.id}&type=${data.provider}`,
|
||||
// );
|
||||
menuStory.jumpPage('media/Device/Channel', {
|
||||
id: data.id,
|
||||
type: data.provider,
|
||||
});
|
||||
menuStory.jumpPage(
|
||||
'media/Device/Channel',
|
||||
{},
|
||||
{
|
||||
id: data.id,
|
||||
type: data.provider,
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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>
|
||||
|
@ -334,23 +333,9 @@ watch(
|
|||
msgType.value = MSG_TYPE[val];
|
||||
|
||||
formData.value.provider =
|
||||
formData.value.provider !== ':id'
|
||||
route.params.id !== ':id'
|
||||
? formData.value.provider
|
||||
: msgType.value[0].value;
|
||||
|
||||
// formData.value.configuration =
|
||||
// CONFIG_FIELD_MAP[val][formData.value.provider];
|
||||
|
||||
// clearValid();
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => formData.value.provider,
|
||||
(val) => {
|
||||
// formData.value.configuration =
|
||||
// CONFIG_FIELD_MAP[formData.value.type][val];
|
||||
// clearValid();
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -422,12 +407,6 @@ const { resetFields, validate, validateInfos, clearValidate } = useForm(
|
|||
formRules.value,
|
||||
);
|
||||
|
||||
const clearValid = () => {
|
||||
setTimeout(() => {
|
||||
clearValidate();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const getDetail = async () => {
|
||||
if (route.params.id === ':id') return;
|
||||
const res = await configApi.detail(route.params.id as string);
|
||||
|
@ -445,7 +424,7 @@ const handleTypeChange = () => {
|
|||
setTimeout(() => {
|
||||
formData.value.configuration =
|
||||
CONFIG_FIELD_MAP[formData.value.type][formData.value.provider];
|
||||
// resetPublicFiles();
|
||||
resetPublicFiles();
|
||||
}, 0);
|
||||
};
|
||||
|
||||
|
@ -455,7 +434,48 @@ const handleTypeChange = () => {
|
|||
const handleProviderChange = () => {
|
||||
formData.value.configuration =
|
||||
CONFIG_FIELD_MAP[formData.value.type][formData.value.provider];
|
||||
// resetPublicFiles();
|
||||
resetPublicFiles();
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置字段值
|
||||
*/
|
||||
const resetPublicFiles = () => {
|
||||
switch (formData.value.provider) {
|
||||
case 'dingTalkMessage':
|
||||
formData.value.configuration.appKey = '';
|
||||
formData.value.configuration.appSecret = '';
|
||||
break;
|
||||
case 'dingTalkRobotWebHook':
|
||||
formData.value.configuration.url = '';
|
||||
break;
|
||||
case 'corpMessage':
|
||||
formData.value.configuration.corpId = '';
|
||||
formData.value.configuration.corpSecret = '';
|
||||
break;
|
||||
case 'embedded':
|
||||
formData.value.configuration.host = '';
|
||||
formData.value.configuration.port = 25;
|
||||
formData.value.configuration.ssl = false;
|
||||
formData.value.configuration.sender = '';
|
||||
formData.value.configuration.username = '';
|
||||
formData.value.configuration.password = '';
|
||||
break;
|
||||
case 'aliyun':
|
||||
formData.value.configuration.regionId = '';
|
||||
formData.value.configuration.accessKeyId = '';
|
||||
formData.value.configuration.secret = '';
|
||||
break;
|
||||
case 'aliyunSms':
|
||||
formData.value.configuration.regionId = '';
|
||||
formData.value.configuration.accessKeyId = '';
|
||||
formData.value.configuration.secret = '';
|
||||
break;
|
||||
case 'http':
|
||||
formData.value.configuration.url = undefined;
|
||||
formData.value.configuration.headers = [];
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
@cancel="_vis = false"
|
||||
width="80%"
|
||||
>
|
||||
<a-row :gutter="10">
|
||||
<a-row :gutter="10" class="model-body">
|
||||
<a-col :span="4">
|
||||
<a-input
|
||||
v-model:value="deptName"
|
||||
|
@ -40,6 +40,7 @@
|
|||
:dataSource="dataSource"
|
||||
:loading="tableLoading"
|
||||
model="table"
|
||||
noPagination
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-button type="primary" @click="handleAutoBind">
|
||||
|
@ -273,14 +274,24 @@ const getActions = (
|
|||
* 自动绑定
|
||||
*/
|
||||
const handleAutoBind = () => {
|
||||
configApi.dingTalkBindUser([], props.data.id).then(() => {
|
||||
const arr = dataSource.value
|
||||
.filter((item: any) => item.userId && item.status.value === 'error')
|
||||
.map((i: any) => {
|
||||
return {
|
||||
userId: i.userId,
|
||||
providerName: i.userName,
|
||||
thirdPartyUserId: i.thirdPartyUserId,
|
||||
};
|
||||
});
|
||||
// console.log('arr: ', arr);
|
||||
configApi.dingTalkBindUser(arr, props.data.id).then(() => {
|
||||
message.success('操作成功');
|
||||
getTableData();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取钉钉部门用户
|
||||
* 获取钉钉/微信部门用户
|
||||
*/
|
||||
const getDeptUsers = async () => {
|
||||
let res = null;
|
||||
|
@ -304,14 +315,24 @@ const getBindUsers = async () => {
|
|||
return res?.result;
|
||||
};
|
||||
/**
|
||||
* 获取所有用户
|
||||
* 获取所有用户未绑定的用户
|
||||
*/
|
||||
const allUserList = ref([]);
|
||||
const getAllUsers = async () => {
|
||||
const { result } = await configApi.getPlatformUsers();
|
||||
const params = {
|
||||
paging: false,
|
||||
terms: [
|
||||
{
|
||||
column: `id$user-third$${props.data.type}_${props.data.provider}$not`,
|
||||
value: props.data.id,
|
||||
},
|
||||
],
|
||||
};
|
||||
const { result } = await configApi.getPlatformUsers(params);
|
||||
allUserList.value = result.map((m: any) => ({
|
||||
label: m.name,
|
||||
value: m.id,
|
||||
...m,
|
||||
}));
|
||||
return result;
|
||||
};
|
||||
|
@ -326,31 +347,36 @@ const getTableData = () => {
|
|||
Promise.all<any>([getDeptUsers(), getBindUsers(), getAllUsers()]).then(
|
||||
(res) => {
|
||||
dataSource.value = [];
|
||||
const [deptUsers, bindUsers, allUsers] = res;
|
||||
(deptUsers || []).forEach((item: any) => {
|
||||
const [deptUsers, bindUsers, unBindUsers] = res;
|
||||
(deptUsers || []).forEach((deptUser: any) => {
|
||||
// 未绑定的用户
|
||||
let unBindUser = unBindUsers.find(
|
||||
(f: any) => f.name === deptUser?.name,
|
||||
);
|
||||
// 绑定的用户
|
||||
const bindUser = bindUsers.find(
|
||||
(f: any) => f.thirdPartyUserId === item.id,
|
||||
);
|
||||
// 平台用户
|
||||
const allUser = allUsers.find(
|
||||
(f: any) => f.id === bindUser?.userId,
|
||||
(f: any) => f.thirdPartyUserId === deptUser.id,
|
||||
);
|
||||
if (bindUser) {
|
||||
unBindUser = unBindUsers.find(
|
||||
(f: any) => f.id === bindUser.userId,
|
||||
);
|
||||
}
|
||||
dataSource.value.push({
|
||||
thirdPartyUserId: item.id,
|
||||
thirdPartyUserName: item.name,
|
||||
userId: bindUser?.userId,
|
||||
userName: allUser
|
||||
? `${allUser.name}(${allUser.username})`
|
||||
thirdPartyUserId: deptUser.id,
|
||||
thirdPartyUserName: deptUser.name,
|
||||
bindId: bindUser?.userId,
|
||||
userId: unBindUser?.id,
|
||||
userName: unBindUser
|
||||
? `${unBindUser.name}(${unBindUser.username})`
|
||||
: '',
|
||||
status: {
|
||||
text: bindUser?.providerName ? '已绑定' : '未绑定',
|
||||
value: bindUser?.providerName ? 'success' : 'error',
|
||||
},
|
||||
bindId: bindUser?.id,
|
||||
});
|
||||
});
|
||||
console.log('dataSource.value: ', dataSource.value);
|
||||
// console.log('dataSource.value: ', dataSource.value);
|
||||
},
|
||||
);
|
||||
tableLoading.value = false;
|
||||
|
@ -369,7 +395,11 @@ watch(
|
|||
*/
|
||||
const bindVis = ref(false);
|
||||
const confirmLoading = ref(false);
|
||||
const formData = ref({ userId: '' });
|
||||
const formData = ref({
|
||||
userId: '',
|
||||
thirdPartyUserId: '',
|
||||
thirdPartyUserName: '',
|
||||
});
|
||||
const formRules = ref({
|
||||
userId: [{ required: true, message: '请选择用户', trigger: 'change' }],
|
||||
});
|
||||
|
@ -381,7 +411,8 @@ const { resetFields, validate, validateInfos, clearValidate } = useForm(
|
|||
|
||||
const handleBind = (row: any) => {
|
||||
bindVis.value = true;
|
||||
formData.value = row;
|
||||
// formData.value = row;
|
||||
Object.assign(formData.value, row);
|
||||
getAllUsers();
|
||||
};
|
||||
|
||||
|
@ -402,8 +433,8 @@ const filterOption = (input: string, option: any) => {
|
|||
const handleBindSubmit = () => {
|
||||
validate().then(async () => {
|
||||
const params = {
|
||||
// providerName: formData.value.thirdPartyUserName,
|
||||
// thirdPartyUserId: formData.value.thirdPartyUserId,
|
||||
providerName: formData.value.thirdPartyUserName,
|
||||
thirdPartyUserId: formData.value.thirdPartyUserId,
|
||||
userId: formData.value.userId,
|
||||
};
|
||||
confirmLoading.value = true;
|
||||
|
@ -434,8 +465,13 @@ const handleBindSubmit = () => {
|
|||
};
|
||||
const handleCancel = () => {
|
||||
bindVis.value = false;
|
||||
resetFields()
|
||||
resetFields();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.model-body {
|
||||
height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<a-button>导入</a-button>
|
||||
</a-upload>
|
||||
<a-popconfirm
|
||||
title="确认导出当前页数据?"
|
||||
title="确认导出?"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="handleExport"
|
||||
|
@ -308,7 +308,7 @@ const beforeUpload = (file: any) => {
|
|||
* 导出
|
||||
*/
|
||||
const handleExport = () => {
|
||||
downloadObject(configRef.value.dataSource, `通知配置`);
|
||||
downloadObject(configRef.value._dataSource, `通知配置`);
|
||||
};
|
||||
|
||||
const syncVis = ref(false);
|
||||
|
|
|
@ -92,10 +92,7 @@ const handleChange = (info: UploadChangeParam, id: string | undefined) => {
|
|||
const targetFileIdx = fileList.value.findIndex((f) => f.id === id);
|
||||
fileList.value[targetFileIdx].name = info.file.name;
|
||||
fileList.value[targetFileIdx].location = info.file.response?.result;
|
||||
emit(
|
||||
'update:attachments',
|
||||
fileList.value.map(({ name, location }) => ({ name, location })),
|
||||
);
|
||||
emitEvents();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -107,6 +104,7 @@ const handleDelete = (id: string | undefined) => {
|
|||
const idx = fileList.value.findIndex((f) => f.id === id);
|
||||
|
||||
fileList.value.splice(idx, 1);
|
||||
emitEvents();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -118,6 +116,15 @@ const handleAdd = () => {
|
|||
name: '',
|
||||
location: '',
|
||||
});
|
||||
|
||||
emitEvents();
|
||||
};
|
||||
|
||||
const emitEvents = () => {
|
||||
emit(
|
||||
'update:attachments',
|
||||
fileList.value.map(({ name, location }) => ({ name, location })),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -106,7 +106,8 @@
|
|||
<a-form-item label="收信部门">
|
||||
<ToOrg
|
||||
v-model:toParty="
|
||||
formData.template.toParty
|
||||
formData.template
|
||||
.departmentIdList
|
||||
"
|
||||
:type="formData.type"
|
||||
:config-id="formData.configId"
|
||||
|
@ -132,7 +133,7 @@
|
|||
</template>
|
||||
<ToUser
|
||||
v-model:toUser="
|
||||
formData.template.toUser
|
||||
formData.template.userIdList
|
||||
"
|
||||
:type="formData.type"
|
||||
:config-id="formData.configId"
|
||||
|
@ -157,6 +158,7 @@
|
|||
formData.template.messageType
|
||||
"
|
||||
placeholder="请选择消息类型"
|
||||
@change="handleMessageTypeChange"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(
|
||||
|
@ -480,7 +482,11 @@
|
|||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item>
|
||||
<a-form-item
|
||||
v-bind="
|
||||
validateInfos['template.calledShowNumbers']
|
||||
"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
被叫显号
|
||||
|
@ -713,12 +719,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 +762,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();
|
||||
|
@ -794,25 +801,59 @@ const formData = ref<TemplateFormData>({
|
|||
});
|
||||
|
||||
/**
|
||||
* 重置公用字段值
|
||||
* 重置字段值
|
||||
*/
|
||||
const resetPublicFiles = () => {
|
||||
formData.value.template.message = '';
|
||||
formData.value.configId = undefined;
|
||||
|
||||
if (
|
||||
formData.value.provider === 'dingTalkMessage' ||
|
||||
formData.value.type === 'weixin'
|
||||
) {
|
||||
formData.value.template.toTag = undefined;
|
||||
formData.value.template.toUser = undefined;
|
||||
formData.value.template.agentId = undefined;
|
||||
switch (formData.value.provider) {
|
||||
case 'dingTalkMessage':
|
||||
formData.value.template.agentId = '';
|
||||
formData.value.template.message = '';
|
||||
formData.value.template.departmentIdList = '';
|
||||
formData.value.template.userIdList = '';
|
||||
break;
|
||||
case 'dingTalkRobotWebHook':
|
||||
formData.value.template.message = '';
|
||||
formData.value.template.messageType = 'markdown';
|
||||
formData.value.template.markdown = { text: '', title: '' };
|
||||
break;
|
||||
case 'corpMessage':
|
||||
formData.value.template.agentId = '';
|
||||
formData.value.template.message = '';
|
||||
formData.value.template.toParty = '';
|
||||
formData.value.template.toUser = '';
|
||||
formData.value.template.toTag = '';
|
||||
break;
|
||||
case 'embedded':
|
||||
formData.value.template.subject = '';
|
||||
formData.value.template.message = '';
|
||||
formData.value.template.text = '';
|
||||
formData.value.template.sendTo = [];
|
||||
formData.value.template.attachments = [];
|
||||
break;
|
||||
case 'aliyun':
|
||||
formData.value.template.templateType = 'tts';
|
||||
formData.value.template.templateCode = '';
|
||||
formData.value.template.ttsCode = '';
|
||||
formData.value.template.message = '';
|
||||
formData.value.template.playTimes = 1;
|
||||
formData.value.template.calledShowNumbers = '';
|
||||
formData.value.template.calledNumber = '';
|
||||
break;
|
||||
case 'aliyunSms':
|
||||
formData.value.template.code = '';
|
||||
formData.value.template.message = '';
|
||||
formData.value.template.phoneNumber = '';
|
||||
formData.value.template.signName = '';
|
||||
break;
|
||||
case 'http':
|
||||
formData.value.template.contextAsBody = true;
|
||||
formData.value.template.body = '';
|
||||
break;
|
||||
}
|
||||
if (formData.value.type === 'weixin')
|
||||
formData.value.template.toParty = undefined;
|
||||
if (formData.value.type === 'email')
|
||||
formData.value.template.toParty = undefined;
|
||||
// formData.value.description = '';
|
||||
|
||||
formData.value.configId = undefined;
|
||||
formData.value.variableDefinitions = [];
|
||||
handleMessageTypeChange();
|
||||
};
|
||||
|
||||
// 根据通知方式展示对应的字段
|
||||
|
@ -824,15 +865,8 @@ watch(
|
|||
route.params.id !== ':id'
|
||||
? formData.value.provider
|
||||
: msgType.value[0].value;
|
||||
// formData.value.provider = formData.value.provider || msgType.value[0].value;
|
||||
// console.log('formData.value.template: ', formData.value.template);
|
||||
|
||||
// formData.value.template =
|
||||
// TEMPLATE_FIELD_MAP[val][formData.value.provider];
|
||||
|
||||
if (val !== 'email') getConfigList();
|
||||
// clearValid();
|
||||
// console.log('formData.value: ', formData.value);
|
||||
|
||||
if (val === 'sms') {
|
||||
getTemplateList();
|
||||
|
@ -841,15 +875,6 @@ watch(
|
|||
},
|
||||
);
|
||||
|
||||
// watch(
|
||||
// () => formData.value.provider,
|
||||
// (val) => {
|
||||
// formData.value.template = TEMPLATE_FIELD_MAP[formData.value.type][val];
|
||||
|
||||
// clearValid();
|
||||
// },
|
||||
// );
|
||||
|
||||
// 验证规则
|
||||
const formRules = ref({
|
||||
type: [{ required: true, message: '请选择通知方式' }],
|
||||
|
@ -862,7 +887,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 +908,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 +930,127 @@ 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();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
// 钉钉机器人link标题变量提取
|
||||
watch(
|
||||
() => formData.value.template.link?.title,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
variableReg();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
// 邮件标题变量提取
|
||||
watch(
|
||||
() => formData.value.template.subject,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
variableReg();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// const clearValid = () => {
|
||||
// setTimeout(() => {
|
||||
// formData.value.variableDefinitions = [];
|
||||
// clearValidate();
|
||||
// }, 200);
|
||||
// };
|
||||
// 模板内容变量提取
|
||||
watch(
|
||||
() => formData.value.template.message,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
variableReg();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
// webhook请求体变量提取
|
||||
watch(
|
||||
() => formData.value.template.body,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
variableReg();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
/**
|
||||
* 将需要提取变量的字段值拼接为一个字符串, 用于统一提取变量
|
||||
*/
|
||||
const spliceStr = () => {
|
||||
let variableFieldsStr = formData.value.template.message;
|
||||
if (formData.value.provider === 'dingTalkRobotWebHook') {
|
||||
if (formData.value.template.messageType === 'markdown')
|
||||
variableFieldsStr += formData.value.template.markdown
|
||||
?.title as string;
|
||||
if (formData.value.template.messageType === 'link')
|
||||
variableFieldsStr += formData.value.template.link?.title as string;
|
||||
}
|
||||
if (formData.value.provider === 'embedded')
|
||||
variableFieldsStr += formData.value.template.subject as string;
|
||||
if (formData.value.provider === 'http')
|
||||
variableFieldsStr += formData.value.template.body as string;
|
||||
// console.log('variableFieldsStr: ', variableFieldsStr);
|
||||
return variableFieldsStr || '';
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据字段输入内容, 提取变量
|
||||
* @param value
|
||||
*/
|
||||
const variableReg = () => {
|
||||
const _val = spliceStr();
|
||||
// 已经存在的变量
|
||||
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[];
|
||||
};
|
||||
|
||||
/**
|
||||
* 钉钉机器人 消息类型选择改变
|
||||
*/
|
||||
const handleMessageTypeChange = () => {
|
||||
delete formData.value.template.markdown;
|
||||
delete formData.value.template.link;
|
||||
delete formData.value.template.text;
|
||||
if (formData.value.template.messageType === 'link') {
|
||||
formData.value.template.link = {
|
||||
title: '',
|
||||
picUrl: '',
|
||||
messageUrl: '',
|
||||
text: formData.value.template.message as string,
|
||||
};
|
||||
}
|
||||
if (formData.value.template.messageType === 'markdown') {
|
||||
formData.value.template.markdown = {
|
||||
title: '',
|
||||
text: formData.value.template.message as string,
|
||||
};
|
||||
}
|
||||
if (formData.value.template.messageType === 'text') {
|
||||
formData.value.template.text = {
|
||||
content: formData.value.template.message as string,
|
||||
};
|
||||
}
|
||||
formData.value.variableDefinitions = [];
|
||||
formData.value.template.message = '';
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取详情
|
||||
|
@ -951,6 +1085,7 @@ const handleTypeChange = () => {
|
|||
setTimeout(() => {
|
||||
formData.value.template =
|
||||
TEMPLATE_FIELD_MAP[formData.value.type][formData.value.provider];
|
||||
// console.log('formData.value.template: ', formData.value.template);
|
||||
resetPublicFiles();
|
||||
}, 0);
|
||||
};
|
||||
|
@ -961,6 +1096,8 @@ const handleTypeChange = () => {
|
|||
const handleProviderChange = () => {
|
||||
formData.value.template =
|
||||
TEMPLATE_FIELD_MAP[formData.value.type][formData.value.provider];
|
||||
// console.log('formData.value: ', formData.value);
|
||||
// console.log('formData.value.template: ', formData.value.template);
|
||||
getConfigList();
|
||||
resetPublicFiles();
|
||||
};
|
||||
|
@ -1026,8 +1163,9 @@ const handleSubmit = () => {
|
|||
setTimeout(() => {
|
||||
validate()
|
||||
.then(async () => {
|
||||
formData.value.template.ttsCode =
|
||||
formData.value.template.templateCode;
|
||||
if (formData.value.provider === 'ttsCode')
|
||||
formData.value.template.ttsCode =
|
||||
formData.value.template.templateCode;
|
||||
btnLoading.value = true;
|
||||
let res;
|
||||
if (!formData.value.id) {
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<a-button>导入</a-button>
|
||||
</a-upload>
|
||||
<a-popconfirm
|
||||
title="确认导出当前页数据?"
|
||||
title="确认导出?"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="handleExport"
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增
|
||||
|
@ -298,7 +314,7 @@ const beforeUpload = (file: any) => {
|
|||
* 导出
|
||||
*/
|
||||
const handleExport = () => {
|
||||
downloadObject(configRef.value.dataSource, `通知配置`);
|
||||
downloadObject(configRef.value._dataSource, `通知配置`);
|
||||
};
|
||||
|
||||
const syncVis = ref(false);
|
||||
|
|
|
@ -27,16 +27,22 @@ interface ILink {
|
|||
messageUrl: string;
|
||||
text: string;
|
||||
}
|
||||
interface IText {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export type TemplateFormData = {
|
||||
template: {
|
||||
// 钉钉消息
|
||||
agentId?: string;
|
||||
message?: string;
|
||||
departmentIdList?: string;
|
||||
userIdList?: string;
|
||||
// 钉钉机器人
|
||||
messageType?: string;
|
||||
markdown?: IMarkDown;
|
||||
link?: ILink;
|
||||
text?: IText;
|
||||
// 微信
|
||||
// agentId?: string;
|
||||
// message?: string;
|
||||
|
|
|
@ -147,10 +147,12 @@ export const TEMPLATE_FIELD_MAP = {
|
|||
dingTalkMessage: {
|
||||
agentId: '',
|
||||
message: '',
|
||||
departmentIdList: '',
|
||||
userIdList: ''
|
||||
},
|
||||
dingTalkRobotWebHook: {
|
||||
message: '',
|
||||
messageType: '',
|
||||
messageType: 'markdown',
|
||||
markdown: {
|
||||
text: '',
|
||||
title: '',
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-form layout="vertical" :rules="rule" :model="form" ref="formRef">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="名称" name="name">
|
||||
<a-input
|
||||
placeholder="请输入名称"
|
||||
v-model:value="form.name"
|
||||
></a-input> </a-form-item
|
||||
></a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="类型" name="targetType">
|
||||
<a-select
|
||||
:options="options"
|
||||
v-model:value="form.targetType"
|
||||
:disabled="selectDisable"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="级别" name="level">
|
||||
<a-radio-group v-model:value="form.level">
|
||||
<a-radio-button
|
||||
v-for="(item, index) in levelOption"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
font-size: 15px;
|
||||
width: 90%;
|
||||
"
|
||||
>
|
||||
<img
|
||||
:src="getImage(`/alarm/alarm${index + 1}.png`)"
|
||||
style="height: 40px"
|
||||
alt=""
|
||||
/>{{ item.label }}
|
||||
</div>
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="说明" name="description">
|
||||
<a-textarea v-model:value="form.description"></a-textarea>
|
||||
</a-form-item>
|
||||
<a-button type="primary" @click="handleSave" :loading="loading"
|
||||
>保存</a-button
|
||||
>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getTargetTypes, save, detail } from '@/api/rule-engine/configuration';
|
||||
import { queryLevel } from '@/api/rule-engine/config';
|
||||
import { query } from '@/api/rule-engine/scene';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { Store } from 'jetlinks-store';
|
||||
const route = useRoute();
|
||||
const id = route.query?.id;
|
||||
let selectDisable = ref(false);
|
||||
const queryData = () => {
|
||||
if (id) {
|
||||
detail(id).then((res) => {
|
||||
if (res.status === 200) {
|
||||
form.level = res?.result?.level;
|
||||
form.name = res?.result?.name;
|
||||
form.targetType = res?.result?.targetType;
|
||||
form.description = res?.result?.description;
|
||||
Store.set('configuration-data', res.result);
|
||||
query({
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'id',
|
||||
termType: 'alarm-bind-rule',
|
||||
value: id,
|
||||
},
|
||||
],
|
||||
type: 'and',
|
||||
},
|
||||
],
|
||||
sorts: [
|
||||
{
|
||||
name: 'createTime',
|
||||
order: 'desc',
|
||||
},
|
||||
],
|
||||
}).then((resq) => {
|
||||
if (resq.status === 200) {
|
||||
selectDisable.value = !!resq.result.data?.length;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
const rule = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
],
|
||||
targetType: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择类型',
|
||||
},
|
||||
],
|
||||
level: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择级别',
|
||||
},
|
||||
],
|
||||
description: [
|
||||
{
|
||||
max: 200,
|
||||
message: '最多可输入200个字符',
|
||||
},
|
||||
],
|
||||
};
|
||||
let form = reactive({
|
||||
level: '',
|
||||
targetType: '',
|
||||
name: '',
|
||||
description: '',
|
||||
});
|
||||
let options = ref();
|
||||
let levelOption = ref();
|
||||
let loading = ref(false);
|
||||
const formRef = ref();
|
||||
const menuStory = useMenuStore();
|
||||
const getSupports = async () => {
|
||||
let res = await getTargetTypes();
|
||||
if (res.status === 200) {
|
||||
options.value = res.result.map(
|
||||
(item: { id: string; name: string }) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
getSupports();
|
||||
const getLevel = () => {
|
||||
queryLevel().then((res) => {
|
||||
if (res.status === 200) {
|
||||
levelOption.value = res.result?.levels
|
||||
?.filter((i: any) => i?.level && i?.title)
|
||||
.map((item: { level: number; title: string }) => ({
|
||||
label: item.title,
|
||||
value: item.level,
|
||||
}));
|
||||
}
|
||||
});
|
||||
};
|
||||
getLevel();
|
||||
const handleSave = async () => {
|
||||
loading.value = true;
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
const res = await save(form);
|
||||
loading.value = false;
|
||||
if (res.status === 200) {
|
||||
message.success('操作成功');
|
||||
menuStory.jumpPage(
|
||||
'rule-engine/Alarm/Configuration/Save',
|
||||
{},
|
||||
{ id: res.result?.id },
|
||||
);
|
||||
if (!id) {
|
||||
Store.set('configuration-data', res.result);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
loading.value = false;
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
queryData();
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.ant-radio-button-wrapper {
|
||||
margin: 10px 15px 0 0;
|
||||
width: 125px;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,9 @@
|
|||
<template>
|
||||
<div>123</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<a-card>
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="1" tab="基础配置">
|
||||
<Base/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="关联场景联动">
|
||||
<Scene></Scene>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="告警记录"></a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Base from './Base/index.vue';
|
||||
import Scene from './Scene/index.vue'
|
||||
const activeKey = ref('2');
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,471 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<div>
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="device-instance"
|
||||
@search="handleSearch"
|
||||
></Search>
|
||||
<JTable
|
||||
:columns="columns"
|
||||
:request="queryList"
|
||||
ref="tableRef"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
}"
|
||||
:params="params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="add"
|
||||
><plus-outlined />新增</a-button
|
||||
>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value="slotProps"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
v-bind="slotProps"
|
||||
:status="slotProps.state?.value"
|
||||
:statusText="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
enabled: 'success',
|
||||
disabled: 'error',
|
||||
}"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img
|
||||
:src="getImage('/alarm/alarm-config.png')"
|
||||
/>
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis>
|
||||
<span style="font-weight: 600; font-size: 16px">
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="content-des-title">
|
||||
关联场景联动
|
||||
</div>
|
||||
<Ellipsis
|
||||
><div>
|
||||
{{ (slotProps?.scene || []).map((item: any) => item?.name).join(',') || '' }}
|
||||
</div></Ellipsis
|
||||
>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div class="content-des-title">
|
||||
告警级别
|
||||
</div>
|
||||
<div>
|
||||
{{ (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,
|
||||
getScene,
|
||||
} 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';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import encodeQuery from '@/utils/encodeQuery';
|
||||
import { useStorage } from '@vueuse/core';
|
||||
const params = ref<Record<string, any>>({});
|
||||
let isAdd = ref<number>(0);
|
||||
let title = ref<string>('');
|
||||
const tableRef = ref<Record<string, any>>({});
|
||||
const menuStory = useMenuStore();
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'targetType',
|
||||
key: 'targetType',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: '产品',
|
||||
value: 'product',
|
||||
},
|
||||
{
|
||||
label: '设备',
|
||||
value: 'device',
|
||||
},
|
||||
{
|
||||
label: '组织',
|
||||
value: 'org',
|
||||
},
|
||||
{
|
||||
label: '其他',
|
||||
value: 'other',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '告警级别',
|
||||
dataIndex: 'level',
|
||||
key: 'level',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: async () => {
|
||||
const res = await queryLevel();
|
||||
if (res.status === 200) {
|
||||
return (res?.result?.levels || [])
|
||||
.filter((i: any) => i?.level && i?.title)
|
||||
.map((item: any) => ({
|
||||
label: item.title,
|
||||
value: item.level,
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '关联场景联动',
|
||||
dataIndex: 'sceneId',
|
||||
wdith: 250,
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: async () => {
|
||||
const res = await getScene(
|
||||
encodeQuery({
|
||||
sorts: { createTime: 'desc' },
|
||||
}),
|
||||
);
|
||||
if(res.status === 200){
|
||||
return res.result.map((item:any) => ({label:item.name, value:item.id}))
|
||||
}
|
||||
return [];
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: '正常',
|
||||
value: 'enabled',
|
||||
},
|
||||
{
|
||||
label: '禁用',
|
||||
value: 'disabled',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
search:{
|
||||
type:'string',
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
const map = {
|
||||
product: '产品',
|
||||
device: '设备',
|
||||
org: '组织',
|
||||
other: '其他',
|
||||
};
|
||||
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: () => {
|
||||
menuStory.jumpPage('rule-engine/Alarm/Configuration/Save',{},{id:data.id});
|
||||
},
|
||||
},
|
||||
{
|
||||
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;
|
||||
};
|
||||
const add = () => {
|
||||
menuStory.jumpPage('rule-engine/Alarm/Configuration/Save');
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.content-des-title {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,9 @@
|
|||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -1,7 +1,11 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<div>
|
||||
<Search :columns="query.columns" target="device-instance" @search="handleSearch"></Search>
|
||||
<Search
|
||||
:columns="query.columns"
|
||||
target="device-instance"
|
||||
@search="handleSearch"
|
||||
></Search>
|
||||
<JTable
|
||||
:columns="columns"
|
||||
:request="queryList"
|
||||
|
@ -14,7 +18,7 @@
|
|||
<template #headerTitle>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="add"
|
||||
><plus-outlined/>新增</a-button
|
||||
><plus-outlined />新增</a-button
|
||||
>
|
||||
</a-space>
|
||||
</template>
|
||||
|
@ -36,14 +40,18 @@
|
|||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3 style="font-weight: 600">
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
<Ellipsis>
|
||||
<span style="font-weight: 600; font-size: 16px">
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="rule-desc">
|
||||
{{ slotProps.description }}
|
||||
</div>
|
||||
<Ellipsis>
|
||||
<div>
|
||||
{{ slotProps.description }}
|
||||
</div>
|
||||
</Ellipsis>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
@ -154,7 +162,12 @@
|
|||
<script lang="ts" setup>
|
||||
import JTable from '@/components/Table';
|
||||
import type { InstanceItem } from './typings';
|
||||
import { queryList , startRule , stopRule , deleteRule} from '@/api/rule-engine/instance';
|
||||
import {
|
||||
queryList,
|
||||
startRule,
|
||||
stopRule,
|
||||
deleteRule,
|
||||
} from '@/api/rule-engine/instance';
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
@ -193,6 +206,14 @@ const query = {
|
|||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
key: 'description',
|
||||
dataIndex: 'description',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const columns = [
|
||||
|
@ -258,7 +279,10 @@ const getActions = (
|
|||
tooltip: {
|
||||
title: data.state?.value !== 'disable' ? '禁用' : '启用',
|
||||
},
|
||||
icon: data.state?.value !== 'disable' ? 'StopOutlined' : 'CheckCircleOutlined',
|
||||
icon:
|
||||
data.state?.value !== 'disable'
|
||||
? 'StopOutlined'
|
||||
: 'CheckCircleOutlined',
|
||||
popConfirm: {
|
||||
title: `确认${data.state !== 'disable' ? '禁用' : '启用'}?`,
|
||||
onConfirm: async () => {
|
||||
|
@ -320,14 +344,8 @@ const refresh = () => {
|
|||
tableRef.value?.reload();
|
||||
};
|
||||
const handleSearch = (e: any) => {
|
||||
console.log(e);
|
||||
params.value = e;
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.rule-desc {
|
||||
white-space: nowrap; /*强制在同一行内显示所有文本,直到文本结束或者遭遇br标签对象才换行。*/
|
||||
overflow: hidden; /*超出部分隐藏*/
|
||||
text-overflow: ellipsis; /*隐藏部分以省略号代替*/
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,211 @@
|
|||
<template>
|
||||
<div class="does-container">
|
||||
<div v-show="props.type === 'internal-standalone'">
|
||||
<h1>1.概述</h1>
|
||||
<div>
|
||||
内部独立应用适用于将<span>官方开发</span>的其他应用与<span
|
||||
>物联网平台相互集成</span
|
||||
>
|
||||
,例如将可视化平台集成至物联网平台,或者将物联网平台集成至可视化平台。以实现多处访问、集中管控的业务场景。
|
||||
</div>
|
||||
<div>
|
||||
内部独立应用的<span>后端服务</span>相互<span>独立运行</span>,互不影响。
|
||||
</div>
|
||||
<div class="image">
|
||||
<a-image width="100%" :src="img1" />
|
||||
</div>
|
||||
|
||||
<h1>2.接入方式说明</h1>
|
||||
<div>1、页面集成</div>
|
||||
<div>
|
||||
集成其他应用的<span>前端页面</span>至物联网平台中。为实现应用与物联网平台数据互联互通,
|
||||
<span>通常还需要配置API服务</span>。
|
||||
</div>
|
||||
<div>2、API客户端</div>
|
||||
<div>
|
||||
<span>物联网平台</span>请求<span>其他应用</span>
|
||||
的接口,以实现将物联网平台集成至其他应用系统。如需实现<span
|
||||
>其他应用</span
|
||||
>
|
||||
登录后可以访问<span>物联网平台</span>页面,<span>还需要配置单点登录</span>。
|
||||
</div>
|
||||
<div>3、API服务</div>
|
||||
<div>
|
||||
<span>外部应用</span
|
||||
>请求<span>物联网平台</span>的接口,实现物联网平台的服务调用能力,
|
||||
<span>通常还需要配置页面集成</span>。
|
||||
</div>
|
||||
<div>
|
||||
配置API服务后,系统将<span>自动创建</span>对应的<span>“第三方应用”用户</span>。用户的
|
||||
<span>账号/密码</span>分别对应appid/secureKey。
|
||||
</div>
|
||||
<div>
|
||||
第三方用户<span>可调用的API服务</span>在其应用管理卡片的<span
|
||||
>其他-{'>'}赋权</span
|
||||
>
|
||||
页面,进行<span>自定义配置</span>。
|
||||
</div>
|
||||
<div>4、单点登录</div>
|
||||
<div>通过<span>第三方平台账号</span>登录到物联网平台。</div>
|
||||
</div>
|
||||
<div v-show="props.type === 'internal-integrated'">
|
||||
<h1>1.概述</h1>
|
||||
<div>
|
||||
内部集成应用适用于将<span>官方开发</span>的其他应用与<span
|
||||
>物联网平台相互集成</span
|
||||
>
|
||||
,例如将可视化平台集成至物联网平台,或者将物联网平台集成至可视化平台。以实现多处访问、集中管控的业务场景。
|
||||
</div>
|
||||
<div>内部独立应用的<span>后端服务在同一个环境运行</span>。</div>
|
||||
<div class="image">
|
||||
<a-image width="100%" :src="img2" />
|
||||
</div>
|
||||
<h1>2.接入方式说明</h1>
|
||||
<div>1、页面集成</div>
|
||||
<div>
|
||||
集成其他应用的<span>前端页面</span>
|
||||
至物联网平台中。集成后系统顶部将新增对应的应用管理菜单
|
||||
</div>
|
||||
<div>2、API客户端</div>
|
||||
<div>
|
||||
<span>物联网平台</span
|
||||
>去请求其他应用的接口,以实现将物联网平台集成至其他应用
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="props.type === 'dingtalk-ent-app'">
|
||||
<div class="url">
|
||||
钉钉开放平台:
|
||||
<a
|
||||
href="https://open-dev.dingtalk.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
https://open-dev.dingtalk.com
|
||||
</a>
|
||||
</div>
|
||||
<h1>1.概述</h1>
|
||||
<div>钉钉企业内部应用适用于通过钉钉登录<span>物联网平台</span></div>
|
||||
<div class="image">
|
||||
<a-image width="100%" :src="img4" />
|
||||
</div>
|
||||
<h1>2.接入方式说明</h1>
|
||||
<div>1、单点登录</div>
|
||||
<div>通过钉钉账号登录到物联网平台。</div>
|
||||
</div>
|
||||
<div v-show="props.type === 'wechat-webapp'">
|
||||
<div class="url">
|
||||
微信开放平台:
|
||||
<a
|
||||
href="https://open.weixin.qq.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
https://open.weixin.qq.com
|
||||
</a>
|
||||
</div>
|
||||
<h1>1.概述</h1>
|
||||
<div>微信网站应用适用于通过微信授权登录<span>物联网平台</span></div>
|
||||
<div class="image">
|
||||
<a-image width="100%" :src="img3" />
|
||||
</div>
|
||||
<h1>2.接入方式说明</h1>
|
||||
<div>1、单点登录</div>
|
||||
<div>通过微信账号登录到物联网平台。</div>
|
||||
</div>
|
||||
<div v-show="props.type === 'third-party'">
|
||||
<h1>1. 概述</h1>
|
||||
<div>
|
||||
第三方应用适用于<span>第三方应用</span>与<span
|
||||
>物联网平台相互集成</span
|
||||
>
|
||||
。例如将公司业务管理系统集成至物联网平台,或者将物联网平台集成至业务管理系统。以实现多处访问、集中管控的业务场景。
|
||||
</div>
|
||||
<div class="image">
|
||||
<a-image width="100%" :src="img5" />
|
||||
</div>
|
||||
<h1>2.接入方式说明</h1>
|
||||
<div>1、页面集成</div>
|
||||
<div>
|
||||
集成其他应用的<span>前端页面</span>至物联网平台中。为实现应用与物联网平台数据互联互通,
|
||||
<span>还需要配置API服务</span>。
|
||||
</div>
|
||||
<div>2、API客户端</div>
|
||||
<div>
|
||||
<span>物联网平台</span>请求<span>第三方应用</span>
|
||||
的接口,以实现将物联网平台集成至其他应用。如需实现<span>第三方应用</span>登录后可以访问
|
||||
<span>物联网平台</span>页面,<span>还需要配置单点登录</span>。
|
||||
</div>
|
||||
<div>3、API服务</div>
|
||||
<div>
|
||||
<span>第三方应用</span
|
||||
>通过API服务配置,请求物联网平台接口,实现<span
|
||||
>物联网平台</span
|
||||
>
|
||||
的服务调用能力,<span>通常还需要配置页面集成</span>。
|
||||
</div>
|
||||
<div>
|
||||
配置API服务后,系统将<span>自动创建</span>对应的<span>“第三方应用”用户</span>。用户的
|
||||
<span>账号/密码</span>分别对应appid/secureKey。
|
||||
</div>
|
||||
<div>
|
||||
第三方用户<span>可调用的API服务</span>在其应用管理卡片的<span
|
||||
>其他-{'>'}赋权</span
|
||||
>
|
||||
页面,进行<span>自定义配置</span>。
|
||||
</div>
|
||||
<div>4、单点登录</div>
|
||||
<div>通过<span>第三方平台账号</span>登录到物联网平台。</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getImage } from '@/utils/comm';
|
||||
import type { applyType } from '../typing';
|
||||
const props = defineProps<{
|
||||
type: applyType;
|
||||
}>();
|
||||
|
||||
const img1 = getImage('/apply/1.png');
|
||||
const img2 = getImage('/apply/2.png');
|
||||
const img3 = getImage('/apply/3.png');
|
||||
const img4 = getImage('/apply/4.png');
|
||||
const img5 = getImage('/apply/5.png');
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.does-container {
|
||||
padding: 24px;
|
||||
overflow-y: auto;
|
||||
color: rgba(#000, 0.8);
|
||||
font-size: 14px;
|
||||
background-color: #fafafa;
|
||||
|
||||
.url {
|
||||
padding: 8px 16px;
|
||||
color: #2f54eb;
|
||||
background-color: rgba(#a7bdf7, 0.2);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 16px 0;
|
||||
color: rgba(#000, 0.85);
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 6px 0;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.image {
|
||||
margin: 16px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<div class="form-label-container">
|
||||
<span class="text">{{ props.text }}</span>
|
||||
<span class="required" v-show="props.required">*</span>
|
||||
<a-tooltip>
|
||||
<template #title>{{ props.tooltip }}</template>
|
||||
<AIcon type="QuestionCircleOutlined" class="icon" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
text: string;
|
||||
tooltip: string;
|
||||
required?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.form-label-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.required {
|
||||
display: inline-block;
|
||||
color: #ff4d4f;
|
||||
font-size: 14px;
|
||||
font-family: SimSun, sans-serif;
|
||||
line-height: 1;
|
||||
}
|
||||
.icon {
|
||||
color: #00000073;
|
||||
cursor: inherit;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,121 @@
|
|||
<template>
|
||||
<div class="request-table-container">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:pagination="false"
|
||||
size="small"
|
||||
bordered
|
||||
>
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'key'">
|
||||
<a-input v-model:value="record.label" />
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'value'">
|
||||
<a-input v-model:value="record.value" />
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'action'">
|
||||
<a-button
|
||||
type="link"
|
||||
@click="removeRow((current - 1) * 10 + index)"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<a-pagination
|
||||
v-show="props.value.length > 10"
|
||||
v-model:current="current"
|
||||
:page-size="10"
|
||||
:total="props.value.length"
|
||||
show-less-items
|
||||
/>
|
||||
<a-button type="dashed" @click="addRow" class="add-btn">
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { optionsType } from '../typing';
|
||||
|
||||
const emits = defineEmits(['update:value']);
|
||||
const props = defineProps<{
|
||||
value: optionsType;
|
||||
}>();
|
||||
const columns = [
|
||||
{
|
||||
title: 'KEY',
|
||||
dataIndex: 'key',
|
||||
},
|
||||
{
|
||||
title: 'VALUE',
|
||||
dataIndex: 'value',
|
||||
},
|
||||
{
|
||||
title: ' ',
|
||||
dataIndex: 'action',
|
||||
},
|
||||
];
|
||||
|
||||
const current = ref<number>(1);
|
||||
|
||||
const tableData = computed(() => {
|
||||
return props.value.slice((current.value - 1) * 10, current.value * 10);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(n, o) => {
|
||||
if (!o || n.length === o.length) return;
|
||||
// 如果是新增行操作
|
||||
else if (n.length > o.length) {
|
||||
// 若新增后会出现新一页,则跳转到最新的一页
|
||||
if (o.length % 10 === 0 && n.length > 10)
|
||||
current.value = current.value + 1;
|
||||
} else {
|
||||
// 如果是删除行操作
|
||||
// 若删除的行是本页的最后一行,且本页不是第一页,则跳转到上一页
|
||||
if (o.length % 10 === 1 && o.length > 10)
|
||||
current.value = current.value - 1;
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
function removeRow(index: number) {
|
||||
const left = props.value.slice(0, index++);
|
||||
const right = props.value.slice(index, props.value.length);
|
||||
emits('update:value', [...left, ...right]);
|
||||
}
|
||||
function addRow() {
|
||||
const newRow = {
|
||||
label: '',
|
||||
value: '',
|
||||
};
|
||||
console.log(111);
|
||||
|
||||
emits('update:value', [...props.value, newRow]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.request-table-container {
|
||||
width: 100%;
|
||||
:deep(.ant-btn-link) {
|
||||
color: #000000d9;
|
||||
|
||||
&:hover {
|
||||
color: #1d39c4;
|
||||
}
|
||||
}
|
||||
.add-btn {
|
||||
width: 100%;
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<a-card class="save-container">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="14">
|
||||
<EditForm @change-apply-type="chengeType" />
|
||||
</a-col>
|
||||
<a-col :span="10"><Does :type="rightType" /></a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Does from './components/Does.vue';
|
||||
import EditForm from './components/EditForm.vue';
|
||||
import type { applyType } from './typing';
|
||||
|
||||
const rightType = ref<applyType>('internal-standalone');
|
||||
const chengeType = (newType: applyType) => {
|
||||
rightType.value = newType;
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,91 @@
|
|||
export type applyType = 'internal-standalone'
|
||||
| 'wechat-webapp'
|
||||
| 'internal-integrated'
|
||||
| 'dingtalk-ent-app'
|
||||
| 'third-party'
|
||||
export type dictType = {
|
||||
id: string;
|
||||
name: string;
|
||||
children?: dictType
|
||||
}[];
|
||||
|
||||
export type optionsType = {
|
||||
label: string,
|
||||
value: string;
|
||||
disabled?: boolean
|
||||
}[]
|
||||
export type formType = {
|
||||
name: string;
|
||||
provider: applyType;
|
||||
integrationModes: string[];
|
||||
config: string;
|
||||
description: string;
|
||||
page: { // 页面集成
|
||||
baseUrl: string,
|
||||
routeType: 'hash' | 'history',
|
||||
parameters: optionsType
|
||||
},
|
||||
apiClient: { // API客户端
|
||||
baseUrl: string, // 接口地址
|
||||
headers: optionsType, // 请求头
|
||||
parameters: optionsType, // 请求参数
|
||||
authConfig: { // 认证配置
|
||||
type: 'none' | 'bearer' | 'oauth2' | 'basic' | 'other', // 类型, 可选值:none, bearer, oauth2, basic, other
|
||||
bearer: { token: string }, // 授权信息
|
||||
basic: { username: string, password: string }, // 基本信息
|
||||
oauth2: { // OAuth2信息
|
||||
authorizationUrl: string, // 授权地址
|
||||
tokenUrl: string, // token地址
|
||||
redirectUri: string, // 重定向地址
|
||||
clientId: string, // 客户端ID
|
||||
clientSecret: string, // 客户端密钥
|
||||
grantType: 'authorization_code' | 'client_credentials' | '', // 类型
|
||||
accessTokenProperty: string, // token属性名
|
||||
tokenRequestType: 'POST_URI' | 'POST_BODY' | '' // token请求方式, 可选值:POST_URI,POST_BODY
|
||||
}
|
||||
}
|
||||
},
|
||||
apiServer: { // API服务
|
||||
appId: string,
|
||||
secureKey: string, // 密钥
|
||||
redirectUri: string, // 重定向URL
|
||||
roleIdList: string[], // 角色列表
|
||||
orgIdList: string[], // 部门列表
|
||||
ipWhiteList: string, // IP白名单
|
||||
signature: 'MD5' | 'SHA256' | '', // 签名方式, 可选值:MD5,SHA256
|
||||
enableOAuth2: boolean, // 是否启用OAuth2
|
||||
},
|
||||
sso: { // 统一单点登陆集成
|
||||
configuration: { // 配置
|
||||
oauth2: { // Oauth2单点登录配置
|
||||
authorizationUrl: string, // 授权地址
|
||||
redirectUri: string, // 重定向地址
|
||||
clientId: string, // 客户端ID
|
||||
clientSecret: string, // 客户端密钥
|
||||
userInfoUrl: string, // 用户信息接口
|
||||
scope: string, // scope
|
||||
userProperty: { // 用户属性字段信息
|
||||
userId: string, // 用户ID
|
||||
username: string, // 用户名
|
||||
name: string, // 名称
|
||||
avatar: string, // 头像
|
||||
email: string, // 邮箱
|
||||
telephone: string, // 电话
|
||||
description: string, // 说明
|
||||
},
|
||||
grantType: 'authorization_code' | 'client_credentials' | '', // 类型
|
||||
tokenUrl: string, // token地址
|
||||
accessTokenProperty: string, // token属性名
|
||||
tokenRequestType: 'POST_URI' | 'POST_BODY' | '', // token请求方式
|
||||
},
|
||||
appId: string, // 微信单点登录配置
|
||||
appKey: string, // 钉钉单点登录配置
|
||||
appSecret: string, // 钉钉、微信单点登录配置
|
||||
},
|
||||
autoCreateUser: boolean, // 是否自动创建平台用户
|
||||
usernamePrefix: string, // 用户ID前缀
|
||||
roleIdList: string[], // 自动创建平台用户时角色列表
|
||||
orgIdList: string[], // 自动创建平台用户时部门列表
|
||||
defaultPasswd: string, // 默认密码
|
||||
}
|
||||
}
|
|
@ -1,13 +1,399 @@
|
|||
<template>
|
||||
<div>
|
||||
应用管理
|
||||
</div>
|
||||
<page-container class="apply-container">
|
||||
<div class="apply-container">
|
||||
<Search :columns="columns" @search="search" />
|
||||
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:request="getApplyList_api"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
}"
|
||||
:params="params"
|
||||
:gridColumn="3"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<div style="display: flex; align-items: center">
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:add`"
|
||||
type="primary"
|
||||
@click="() => table.toSave()"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
<p style="margin: 0 0 0 30px; color: #0000008c">
|
||||
<AIcon
|
||||
type="ExclamationCircleOutlined"
|
||||
style="margin-right: 12px"
|
||||
/>
|
||||
应用管理将多个应用系统的登录简化为一次登录,实现多处访问、集中管控的业务场景。
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value="slotProps"
|
||||
:actions="table.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('/apply.png')" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3 class="card-item-content-title">
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
类型
|
||||
</div>
|
||||
<div>
|
||||
{{
|
||||
table.getTypeLabel(
|
||||
slotProps.provider,
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
说明
|
||||
</div>
|
||||
<div>{{ slotProps.description }}</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<a-tooltip
|
||||
v-bind="item.tooltip"
|
||||
:title="item.disabled && item.tooltip.title"
|
||||
>
|
||||
<a-dropdown
|
||||
placement="bottomRight"
|
||||
v-if="item.key === 'others'"
|
||||
>
|
||||
<a-button>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item.text }}</span>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item
|
||||
v-for="(o, i) in item.children"
|
||||
:key="i"
|
||||
>
|
||||
<a-button
|
||||
type="link"
|
||||
@click="o.onClick"
|
||||
>
|
||||
<AIcon :type="o.icon" />
|
||||
<span>{{ o.text }}</span>
|
||||
</a-button>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<PermissionButton
|
||||
v-else
|
||||
:uhasPermission="item.permission"
|
||||
:tooltip="item.tooltip"
|
||||
:pop-confirm="item.popConfirm"
|
||||
@click="item.onClick"
|
||||
:disabled="item.disabled"
|
||||
>
|
||||
<AIcon :type="item.icon" />
|
||||
<span v-if="item.key !== 'delete'">{{
|
||||
item.text
|
||||
}}</span>
|
||||
</PermissionButton>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
|
||||
<template #provider="slotProps">
|
||||
{{ table.getTypeLabel(slotProps.provider) }}
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<BadgeStatus
|
||||
:status="slotProps.state.value"
|
||||
:text="slotProps.state.text"
|
||||
:statusNames="{
|
||||
enabled: 'success',
|
||||
disabled: 'error',
|
||||
}"
|
||||
></BadgeStatus>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<PermissionButton
|
||||
v-for="i in table.getActions(slotProps, 'table')"
|
||||
:uhasPermission="i.permission"
|
||||
type="link"
|
||||
:tooltip="i.tooltip"
|
||||
:pop-confirm="i.popConfirm"
|
||||
@click="i.onClick"
|
||||
:disabled="i.disabled"
|
||||
>
|
||||
<AIcon :type="i.icon" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts" name="Apply">
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||
import {
|
||||
getApplyList_api,
|
||||
changeApplyStatus_api,
|
||||
delApply_api,
|
||||
} from '@/api/system/apply';
|
||||
import { ActionsType } from '@/components/Table';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
const permission = 'system/User';
|
||||
const typeOptions = [
|
||||
{
|
||||
label: '内部独立应用',
|
||||
value: 'internal-standalone',
|
||||
},
|
||||
{
|
||||
label: '微信网站应用',
|
||||
value: 'wechat-webapp',
|
||||
},
|
||||
{
|
||||
label: '内部集成应用',
|
||||
value: 'internal-integrated',
|
||||
},
|
||||
{
|
||||
label: '钉钉企业内部应用',
|
||||
value: 'dingtalk-ent-app',
|
||||
},
|
||||
{
|
||||
label: '第三方应用',
|
||||
value: 'third-party',
|
||||
},
|
||||
];
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'provider',
|
||||
key: 'provider',
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: typeOptions,
|
||||
},
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
rename: 'status',
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: '正常',
|
||||
value: 'enabled',
|
||||
},
|
||||
{
|
||||
label: '禁用',
|
||||
value: 'disabled',
|
||||
},
|
||||
],
|
||||
},
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
const params = ref({});
|
||||
const search = (newParams: any) => (params.value = {...newParams});
|
||||
|
||||
const tableRef = ref();
|
||||
const table = {
|
||||
refresh: () => {
|
||||
tableRef.value.reload();
|
||||
},
|
||||
toSave: (id?: string, view = false) => {
|
||||
if (id) menuStory.jumpPage('system/Apply/Save', {}, { id, view });
|
||||
else menuStory.jumpPage('system/Apply/Save');
|
||||
},
|
||||
changeStatus: (row: any) => {
|
||||
const state = row.state.value === 'enabled' ? 'disabled' : 'enabled';
|
||||
changeApplyStatus_api(row.id, { state }).then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功');
|
||||
table.refresh();
|
||||
}
|
||||
});
|
||||
},
|
||||
clickDel: (row: any) => {
|
||||
delApply_api(row.id).then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功');
|
||||
table.refresh();
|
||||
}
|
||||
});
|
||||
},
|
||||
getActions: (
|
||||
data: Partial<Record<string, any>>,
|
||||
type: 'card' | 'table',
|
||||
) => {
|
||||
if (!data) return [];
|
||||
const disabled = data.state.value === 'enabled';
|
||||
|
||||
const result = [
|
||||
{
|
||||
permission: true,
|
||||
key: 'edit',
|
||||
text: '编辑',
|
||||
tooltip: {
|
||||
title: '编辑',
|
||||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => table.toSave(data.id),
|
||||
},
|
||||
{
|
||||
permission: true,
|
||||
key: 'action',
|
||||
text: disabled ? '禁用' : '启用',
|
||||
tooltip: {
|
||||
title: disabled ? '禁用' : '启用',
|
||||
},
|
||||
popConfirm: {
|
||||
title: `确认${disabled ? '禁用' : '启用'}`,
|
||||
onConfirm: () => table.changeStatus(data),
|
||||
},
|
||||
icon: disabled ? 'StopOutlined' : 'PlayCircleOutlined',
|
||||
},
|
||||
{
|
||||
permission: true,
|
||||
key: 'delete',
|
||||
text: '删除',
|
||||
tooltip: {
|
||||
title: disabled ? '请先禁用再删除' : '删除',
|
||||
},
|
||||
popConfirm: {
|
||||
title: '确认删除?',
|
||||
onConfirm: () => table.clickDel(data),
|
||||
},
|
||||
disabled,
|
||||
icon: 'DeleteOutlined',
|
||||
},
|
||||
] as ActionsType[];
|
||||
const otherServers = data.integrationModes.map(
|
||||
(item: any) => item.value as string,
|
||||
);
|
||||
const others = {
|
||||
key: 'others',
|
||||
text: '其他',
|
||||
icon: 'EllipsisOutlined',
|
||||
children: [] as ActionsType[],
|
||||
};
|
||||
// 有集成菜单权限
|
||||
if (otherServers.includes('page'))
|
||||
others.children?.push({
|
||||
permission: true,
|
||||
key: 'page',
|
||||
text: '集成菜单',
|
||||
tooltip: {
|
||||
title: '集成菜单',
|
||||
},
|
||||
icon: 'MenuUnfoldOutlined',
|
||||
onClick: () => {},
|
||||
});
|
||||
// 有api操作权限
|
||||
if (otherServers.includes('apiServer'))
|
||||
others.children?.push(
|
||||
{
|
||||
permission: true,
|
||||
key: 'empowerment',
|
||||
text: '赋权',
|
||||
tooltip: {
|
||||
title: '赋权',
|
||||
},
|
||||
icon: 'icon-fuquan',
|
||||
onClick: () => {},
|
||||
},
|
||||
{
|
||||
permission: true,
|
||||
key: 'viewApi',
|
||||
text: '查看API',
|
||||
tooltip: {
|
||||
title: '查看API',
|
||||
},
|
||||
icon: 'icon-chakanAPI',
|
||||
onClick: () => {},
|
||||
},
|
||||
);
|
||||
// 其他不为空
|
||||
if (others.children.length > 0) {
|
||||
if (type === 'card') {
|
||||
result.splice(result.length - 1, 0, others);
|
||||
} else {
|
||||
result.splice(result.length - 1, 0, ...others.children);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
getTypeLabel: (val: string) => {
|
||||
if (!val) return '';
|
||||
return typeOptions.find((item) => item.value === val)?.label;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
.apply-container {
|
||||
:deep(.ant-table-cell) {
|
||||
.ant-btn-link {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,124 +1,134 @@
|
|||
<template>
|
||||
<div class="data-source-container">
|
||||
<Search :columns="query.columns" @search="query.search" />
|
||||
<page-container>
|
||||
<div class="data-source-container">
|
||||
<Search :columns="query.columns" @search="query.search" />
|
||||
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="getDataSourceList_api"
|
||||
model="TABLE"
|
||||
:params="query.params.value"
|
||||
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
@click="table.openDialog({})"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<BadgeStatus
|
||||
:status="slotProps.state?.value"
|
||||
:text="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
enabled: 'success',
|
||||
disabled: 'error',
|
||||
}"
|
||||
>
|
||||
</BadgeStatus>
|
||||
</template>
|
||||
<template #typeId="slotProps">
|
||||
{{
|
||||
(table.typeOptions.value.length &&
|
||||
table.getTypeLabel(slotProps.typeId)) ||
|
||||
''
|
||||
}}
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="getDataSourceList_api"
|
||||
model="TABLE"
|
||||
:params="query.params.value"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
}"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
}"
|
||||
@click="table.openDialog(slotProps)"
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
@click="table.openDialog({})"
|
||||
>
|
||||
<AIcon type="EditOutlined" />
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:manage`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title:
|
||||
slotProps?.typeId === 'rabbitmq'
|
||||
? '暂不支持管理功能'
|
||||
: table.getRowStatus(slotProps)
|
||||
? '管理'
|
||||
: '请先启用数据源',
|
||||
}"
|
||||
@click="
|
||||
() =>
|
||||
router.push(
|
||||
`/system/DataSource/Management?id=${slotProps.id}`,
|
||||
)
|
||||
"
|
||||
:disabled="slotProps?.typeId === 'rabbitmq' || !table.getRowStatus(slotProps)"
|
||||
>
|
||||
<AIcon type="icon-ziyuankuguanli" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:action`"
|
||||
type="link"
|
||||
:popConfirm="{
|
||||
title: `确定要${
|
||||
table.getRowStatus(slotProps) ? '禁用' : '启用'
|
||||
}吗?`,
|
||||
onConfirm: () => table.clickChangeStatus(slotProps),
|
||||
}"
|
||||
:tooltip="{
|
||||
title: table.getRowStatus(slotProps)
|
||||
? '禁用'
|
||||
: '启用',
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<BadgeStatus
|
||||
:status="slotProps.state?.value"
|
||||
:text="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
enabled: 'success',
|
||||
disabled: 'error',
|
||||
}"
|
||||
>
|
||||
<AIcon
|
||||
:type="
|
||||
table.getRowStatus(slotProps)
|
||||
? 'StopOutlined'
|
||||
: 'PlayCircleOutlined'
|
||||
</BadgeStatus>
|
||||
</template>
|
||||
<template #typeId="slotProps">
|
||||
{{
|
||||
(table.typeOptions.value.length &&
|
||||
table.getTypeLabel(slotProps.typeId)) ||
|
||||
''
|
||||
}}
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
}"
|
||||
@click="table.openDialog(slotProps)"
|
||||
>
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:manage`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title:
|
||||
slotProps?.typeId === 'rabbitmq'
|
||||
? '暂不支持管理功能'
|
||||
: table.getRowStatus(slotProps)
|
||||
? '管理'
|
||||
: '请先启用数据源',
|
||||
}"
|
||||
@click="
|
||||
() =>
|
||||
router.push(
|
||||
`/system/DataSource/Management?id=${slotProps.id}`,
|
||||
)
|
||||
"
|
||||
/>
|
||||
<!-- <AIcon type="PlayCircleOutlined" /> -->
|
||||
</PermissionButton>
|
||||
:disabled="
|
||||
slotProps?.typeId === 'rabbitmq' ||
|
||||
!table.getRowStatus(slotProps)
|
||||
"
|
||||
>
|
||||
<AIcon type="icon-ziyuankuguanli" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:action`"
|
||||
type="link"
|
||||
:popConfirm="{
|
||||
title: `确定要${
|
||||
table.getRowStatus(slotProps)
|
||||
? '禁用'
|
||||
: '启用'
|
||||
}吗?`,
|
||||
onConfirm: () =>
|
||||
table.clickChangeStatus(slotProps),
|
||||
}"
|
||||
:tooltip="{
|
||||
title: table.getRowStatus(slotProps)
|
||||
? '禁用'
|
||||
: '启用',
|
||||
}"
|
||||
>
|
||||
<AIcon
|
||||
:type="
|
||||
table.getRowStatus(slotProps)
|
||||
? 'StopOutlined'
|
||||
: 'PlayCircleOutlined'
|
||||
"
|
||||
/>
|
||||
<!-- <AIcon type="PlayCircleOutlined" /> -->
|
||||
</PermissionButton>
|
||||
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: table.getRowStatus(slotProps)
|
||||
? '请先禁用,再删除'
|
||||
: '删除',
|
||||
}"
|
||||
:popConfirm="{
|
||||
title: `确认删除`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
:disabled="table.getRowStatus(slotProps)"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: table.getRowStatus(slotProps)
|
||||
? '请先禁用,再删除'
|
||||
: '删除',
|
||||
}"
|
||||
:popConfirm="{
|
||||
title: `确认删除`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
:disabled="table.getRowStatus(slotProps)"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
||||
<div class="dialogs">
|
||||
<EditDialog ref="editDialogRef" @confirm="table.refresh" />
|
||||
<div class="dialogs">
|
||||
<EditDialog ref="editDialogRef" @confirm="table.refresh" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DataSource">
|
||||
|
@ -132,7 +142,7 @@ import {
|
|||
getDataSourceList_api,
|
||||
getDataTypeDict_api,
|
||||
changeStatus_api,
|
||||
delDataSource_api
|
||||
delDataSource_api,
|
||||
} from '@/api/system/dataSource';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
|
@ -243,7 +253,7 @@ const table = {
|
|||
key: 'action',
|
||||
scopedSlots: true,
|
||||
width: '200px',
|
||||
fixed:'right'
|
||||
fixed: 'right',
|
||||
},
|
||||
],
|
||||
|
||||
|
@ -298,7 +308,6 @@ table.getTypeOption();
|
|||
|
||||
<style lang="less" scoped>
|
||||
.data-source-container {
|
||||
padding: 24px;
|
||||
:deep(.ant-table-cell) {
|
||||
.ant-btn-link {
|
||||
padding: 0;
|
||||
|
|
|
@ -1,30 +1,32 @@
|
|||
<template>
|
||||
<div class="department-container">
|
||||
<a-card class="department-content">
|
||||
<div class="left">
|
||||
<LeftTree @change="(id) => (departmentId = id)" />
|
||||
</div>
|
||||
<div class="right">
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="product" tab="产品">
|
||||
<Product
|
||||
:parentId="departmentId"
|
||||
@open-device-bind="openDeviceBind"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="device" tab="设备">
|
||||
<Device
|
||||
:parentId="departmentId"
|
||||
v-model:bindBool="bindBool"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="user" tab="用户">
|
||||
<User :parentId="departmentId" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
<page-container>
|
||||
<div class="department-container">
|
||||
<a-card class="department-content">
|
||||
<div class="left">
|
||||
<LeftTree @change="(id) => (departmentId = id)" />
|
||||
</div>
|
||||
<div class="right">
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="product" tab="产品">
|
||||
<Product
|
||||
:parentId="departmentId"
|
||||
@open-device-bind="openDeviceBind"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="device" tab="设备">
|
||||
<Device
|
||||
:parentId="departmentId"
|
||||
v-model:bindBool="bindBool"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="user" tab="用户">
|
||||
<User :parentId="departmentId" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Department">
|
||||
|
@ -46,7 +48,6 @@ const openDeviceBind = () => {
|
|||
|
||||
<style lang="less" scoped>
|
||||
.department-container {
|
||||
padding: 24px;
|
||||
.department-content {
|
||||
:deep(.ant-card-body) {
|
||||
display: flex;
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
<template>
|
||||
<div class="menu-detail-container">
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="basic" tab="基本信息"> <BasicInfo /> </a-tab-pane>
|
||||
<a-tab-pane key="button" tab="按钮管理">
|
||||
<ButtonMange />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<page-container>
|
||||
<div class="menu-detail-container">
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="basic" tab="基本信息">
|
||||
<BasicInfo />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="button" tab="按钮管理">
|
||||
<ButtonMange />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -24,7 +28,7 @@ const activeKey = ref('basic');
|
|||
}
|
||||
.ant-tabs-tabpane {
|
||||
background-color: #f0f2f5;
|
||||
padding: 24px;
|
||||
padding-top: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,73 +1,79 @@
|
|||
<template>
|
||||
<div class="menu-container">
|
||||
<Search :columns="query.columns" @search="query.search" />
|
||||
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="table.getList"
|
||||
model="TABLE"
|
||||
:params="query.params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
@click="table.toDetails({})"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
<a-button
|
||||
style="margin-left: 12px"
|
||||
@click="router.push('/system/Menu/Setting')"
|
||||
>菜单配置</a-button
|
||||
>
|
||||
<!-- <PermissionButton
|
||||
:uhasPermission="true"
|
||||
@click="router.push('/system/Menu/Setting')"
|
||||
>
|
||||
菜单配置
|
||||
</PermissionButton> -->
|
||||
</template>
|
||||
<template #createTime="slotProps">
|
||||
{{ moment(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip>
|
||||
<template #title>查看</template>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
@click="table.toDetails(slotProps)"
|
||||
>
|
||||
<search-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<page-container>
|
||||
<div class="menu-container">
|
||||
<Search :columns="query.columns" @search="query.search" />
|
||||
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="table.getList"
|
||||
model="TABLE"
|
||||
:params="query.params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
:tooltip="{ title: '新增子菜单' }"
|
||||
@click="table.addChildren(slotProps)"
|
||||
@click="table.toDetails({})"
|
||||
>
|
||||
<AIcon type="PlusCircleOutlined" />
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
:tooltip="{ title: '删除' }"
|
||||
:popConfirm="{
|
||||
title: `是否删除该菜单`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
<a-button
|
||||
style="margin-left: 12px"
|
||||
@click="router.push('/system/Menu/Setting')"
|
||||
>菜单配置</a-button
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
</div>
|
||||
<!-- <PermissionButton
|
||||
:uhasPermission="true"
|
||||
@click="router.push('/system/Menu/Setting')"
|
||||
>
|
||||
菜单配置
|
||||
</PermissionButton> -->
|
||||
</template>
|
||||
<template #createTime="slotProps">
|
||||
{{
|
||||
moment(slotProps.createTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
}}
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip>
|
||||
<template #title>查看</template>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
@click="table.toDetails(slotProps)"
|
||||
>
|
||||
<search-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
:tooltip="{ title: '新增子菜单' }"
|
||||
@click="table.addChildren(slotProps)"
|
||||
>
|
||||
<AIcon type="PlusCircleOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
:tooltip="{ title: '删除' }"
|
||||
:popConfirm="{
|
||||
title: `是否删除该菜单`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -269,8 +275,6 @@ const table = reactive({
|
|||
|
||||
<style lang="less" scoped>
|
||||
.menu-container {
|
||||
padding: 24px;
|
||||
|
||||
:deep(.ant-table-cell) {
|
||||
.ant-btn-link {
|
||||
padding: 0;
|
||||
|
|
|
@ -1,120 +1,127 @@
|
|||
<template>
|
||||
<div class="permission-container">
|
||||
<Search :columns="query.columns" @search="query.search" />
|
||||
<page-container>
|
||||
<div class="permission-container">
|
||||
<Search :columns="query.columns" @search="query.search" />
|
||||
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="getPermission_api"
|
||||
model="TABLE"
|
||||
:params="query.params"
|
||||
:defaultParams="{ sorts: [{ name: 'id', order: 'asc' }] }"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
@click="table.openDialog(undefined)"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
<a-dropdown trigger="hover">
|
||||
<a-button>批量操作</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item>
|
||||
<a-upload
|
||||
name="file"
|
||||
action="#"
|
||||
accept=".json"
|
||||
:showUploadList="false"
|
||||
:before-upload="table.clickImport"
|
||||
:disabled="
|
||||
!hasPermission(`${permission}:import`)
|
||||
"
|
||||
>
|
||||
<PermissionButton
|
||||
:hasPermission="`${permission}:import`"
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="getPermission_api"
|
||||
model="TABLE"
|
||||
:params="query.params"
|
||||
:defaultParams="{ sorts: [{ name: 'id', order: 'asc' }] }"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
@click="table.openDialog(undefined)"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
<a-dropdown trigger="hover">
|
||||
<a-button>批量操作</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item>
|
||||
<a-upload
|
||||
name="file"
|
||||
action="#"
|
||||
accept=".json"
|
||||
:showUploadList="false"
|
||||
:before-upload="table.clickImport"
|
||||
:disabled="
|
||||
!hasPermission(
|
||||
`${permission}:import`,
|
||||
)
|
||||
"
|
||||
>
|
||||
导入
|
||||
<PermissionButton
|
||||
:hasPermission="`${permission}:import`"
|
||||
>
|
||||
导入
|
||||
</PermissionButton>
|
||||
</a-upload>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:export`"
|
||||
:popConfirm="{
|
||||
title: `确认导出?`,
|
||||
onConfirm: () =>
|
||||
table.clickExport(),
|
||||
}"
|
||||
>
|
||||
导出
|
||||
</PermissionButton>
|
||||
</a-upload>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:export`"
|
||||
:popConfirm="{
|
||||
title: `确认导出?`,
|
||||
onConfirm: () => table.clickExport(),
|
||||
}"
|
||||
>
|
||||
导出
|
||||
</PermissionButton>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<StatusLabel :status-value="slotProps.status" />
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
}"
|
||||
@click="table.openDialog(slotProps)"
|
||||
>
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<StatusLabel :status-value="slotProps.status" />
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
}"
|
||||
@click="table.openDialog(slotProps)"
|
||||
>
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:action`"
|
||||
type="link"
|
||||
:popConfirm="{
|
||||
title: `确定要${
|
||||
slotProps.status ? '禁用' : '启用'
|
||||
}吗?`,
|
||||
onConfirm: () => table.changeStatus(slotProps),
|
||||
}"
|
||||
:tooltip="{ title: slotProps.status ? '禁用' : '启用' }"
|
||||
>
|
||||
<AIcon
|
||||
:type="
|
||||
slotProps.status
|
||||
? 'StopOutlined'
|
||||
: 'PlayCircleOutlined '
|
||||
"
|
||||
/>
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:action`"
|
||||
type="link"
|
||||
:popConfirm="{
|
||||
title: `确定要${
|
||||
slotProps.status ? '禁用' : '启用'
|
||||
}吗?`,
|
||||
onConfirm: () => table.changeStatus(slotProps),
|
||||
}"
|
||||
:tooltip="{
|
||||
title: slotProps.status ? '禁用' : '启用',
|
||||
}"
|
||||
>
|
||||
<AIcon
|
||||
:type="
|
||||
slotProps.status
|
||||
? 'StopOutlined'
|
||||
: 'PlayCircleOutlined '
|
||||
"
|
||||
/>
|
||||
</PermissionButton>
|
||||
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: slotProps.status
|
||||
? '请先禁用,再删除'
|
||||
: '删除',
|
||||
}"
|
||||
:popConfirm="{
|
||||
title: `确认删除`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
:disabled="slotProps.status"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: slotProps.status
|
||||
? '请先禁用,再删除'
|
||||
: '删除',
|
||||
}"
|
||||
:popConfirm="{
|
||||
title: `确认删除`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
:disabled="slotProps.status"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
||||
<div class="dialogs">
|
||||
<EditDialog ref="editDialogRef" @refresh="table.refresh" />
|
||||
<div class="dialogs">
|
||||
<EditDialog ref="editDialogRef" @refresh="table.refresh" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -281,7 +288,6 @@ const table = reactive({
|
|||
|
||||
<style lang="less" scoped>
|
||||
.permission-container {
|
||||
padding: 24px;
|
||||
|
||||
.ant-dropdown-trigger {
|
||||
margin-left: 12px;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<a-card class="api-page-container">
|
||||
<p>
|
||||
<AIcon type="ExclamationCircleOutlined" style="margin-right: 12px;" />配置系统支持API赋权的范围
|
||||
</p>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="5">
|
||||
<LeftTree @select="treeSelect" :mode="props.mode" />
|
||||
|
@ -117,7 +120,6 @@ function init() {
|
|||
|
||||
<style scoped>
|
||||
.api-page-container {
|
||||
padding: 24px;
|
||||
height: 100%;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<page-container>
|
||||
<Api mode="api" />
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Platforms">
|
||||
|
|
|
@ -1,55 +1,59 @@
|
|||
<template>
|
||||
<div class="relationship-container">
|
||||
<Search :columns="query.columns" @search="query.search" />
|
||||
<page-container>
|
||||
<div class="relationship-container">
|
||||
<Search :columns="query.columns" @search="query.search" />
|
||||
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="getRelationshipList_api"
|
||||
model="TABLE"
|
||||
:params="query.params.value"
|
||||
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
@click="table.openDialog(undefined)"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="getRelationshipList_api"
|
||||
model="TABLE"
|
||||
:params="query.params.value"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
}"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
}"
|
||||
@click="table.openDialog(slotProps)"
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
@click="table.openDialog(undefined)"
|
||||
>
|
||||
<AIcon type="EditOutlined" />
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
}"
|
||||
@click="table.openDialog(slotProps)"
|
||||
>
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
type="link"
|
||||
:tooltip="{ title: '删除' }"
|
||||
:popConfirm="{
|
||||
title: `确认删除`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
:disabled="slotProps.status"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
type="link"
|
||||
:tooltip="{ title: '删除' }"
|
||||
:popConfirm="{
|
||||
title: `确认删除`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
:disabled="slotProps.status"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
||||
<EditDialog ref="editDialogRef" @refresh="table.refresh" />
|
||||
</div>
|
||||
<EditDialog ref="editDialogRef" @refresh="table.refresh" />
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Relationship">
|
||||
|
@ -181,7 +185,6 @@ const table = {
|
|||
|
||||
<style lang="less" scoped>
|
||||
.relationship-container {
|
||||
padding: 24px;
|
||||
:deep(.ant-table-cell) {
|
||||
.ant-btn-link {
|
||||
padding: 0;
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
<template>
|
||||
<div class="details-container">
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="1" tab="权限分配"><Permiss /></a-tab-pane>
|
||||
<a-tab-pane key="2" tab="用户管理"><User /></a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<page-container>
|
||||
<div class="details-container">
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="1" tab="权限分配"><Permiss /></a-tab-pane>
|
||||
<a-tab-pane key="2" tab="用户管理"><User /></a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Detail">
|
||||
|
@ -17,17 +19,9 @@ const activeKey = ref('1');
|
|||
|
||||
<style lang="less" scoped>
|
||||
.details-container {
|
||||
|
||||
|
||||
|
||||
:deep(.ant-tabs-nav-wrap) {
|
||||
background-color: #fff;
|
||||
padding: 24px 0 0 24px;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-content-holder) {
|
||||
padding: 24px;
|
||||
padding-left: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,55 +1,57 @@
|
|||
<template>
|
||||
<a-card class="role-container">
|
||||
<Search :columns="query.columns" />
|
||||
<page-container>
|
||||
<a-card class="role-container">
|
||||
<Search :columns="query.columns" />
|
||||
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="getRoleList_api"
|
||||
model="TABLE"
|
||||
:params="query.params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
@click="table.clickAdd"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
</template>
|
||||
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="getRoleList_api"
|
||||
model="TABLE"
|
||||
:params="query.params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
}"
|
||||
@click="table.clickEdit(slotProps)"
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
@click="table.clickAdd"
|
||||
>
|
||||
<AIcon type="EditOutlined" />
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
:tooltip="{ title: '删除' }"
|
||||
:popConfirm="{
|
||||
title: `确定要删除吗`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
</template>
|
||||
|
||||
<div class="dialogs">
|
||||
<AddDialog ref="addDialogRef" />
|
||||
</div>
|
||||
</a-card>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
}"
|
||||
@click="table.clickEdit(slotProps)"
|
||||
>
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
:tooltip="{ title: '删除' }"
|
||||
:popConfirm="{
|
||||
title: `确定要删除吗`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
||||
<div class="dialogs">
|
||||
<AddDialog ref="addDialogRef" />
|
||||
</div>
|
||||
</a-card>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Role">
|
||||
|
@ -146,12 +148,10 @@ nextTick(() => {
|
|||
|
||||
<style lang="less" scoped>
|
||||
.role-container {
|
||||
|
||||
:deep(.ant-table-cell) {
|
||||
.ant-btn-link {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,46 +1,49 @@
|
|||
<template>
|
||||
<div class="user-container">
|
||||
<Search :columns="query.columns" @search="query.search" />
|
||||
<page-container>
|
||||
<div class="user-container">
|
||||
<Search :columns="query.columns" @search="query.search" />
|
||||
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="getUserList_api"
|
||||
model="TABLE"
|
||||
:params="query.params.value"
|
||||
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<!-- <a-button
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="getUserList_api"
|
||||
model="TABLE"
|
||||
:params="query.params.value"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
}"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<!-- <a-button
|
||||
type="primary"
|
||||
@click="table.openDialog('add')"
|
||||
style="margin-right: 10px"
|
||||
><AIcon type="PlusOutlined" />新增</a-button
|
||||
> -->
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:add`"
|
||||
type="primary"
|
||||
@click="table.openDialog('add')"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
</template>
|
||||
<template #type="slotProps">
|
||||
{{ slotProps.type.name }}
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<BadgeStatus
|
||||
:status="slotProps.status"
|
||||
:text="slotProps.status ? '正常' : '禁用'"
|
||||
:statusNames="{
|
||||
1: 'success',
|
||||
0: 'error',
|
||||
}"
|
||||
></BadgeStatus>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<!-- <a-tooltip>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:add`"
|
||||
type="primary"
|
||||
@click="table.openDialog('add')"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
</template>
|
||||
<template #type="slotProps">
|
||||
{{ slotProps.type.name }}
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<BadgeStatus
|
||||
:status="slotProps.status"
|
||||
:text="slotProps.status ? '正常' : '禁用'"
|
||||
:statusNames="{
|
||||
1: 'success',
|
||||
0: 'error',
|
||||
}"
|
||||
></BadgeStatus>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<!-- <a-tooltip>
|
||||
<template #title>编辑</template>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
|
@ -50,7 +53,7 @@
|
|||
<AIcon type="EditOutlined" />
|
||||
</a-button>
|
||||
</a-tooltip> -->
|
||||
<!-- <a-popconfirm
|
||||
<!-- <a-popconfirm
|
||||
:title="`确定${slotProps.status ? '禁用' : '启用'}吗?`"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
|
@ -66,7 +69,7 @@
|
|||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-popconfirm> -->
|
||||
<!-- <a-tooltip>
|
||||
<!-- <a-tooltip>
|
||||
<template #title>重置密码</template>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
|
@ -76,7 +79,7 @@
|
|||
<AIcon type="icon-zhongzhimima" />
|
||||
</a-button>
|
||||
</a-tooltip> -->
|
||||
<!-- <a-popconfirm
|
||||
<!-- <a-popconfirm
|
||||
title="确认删除"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
|
@ -97,66 +100,67 @@
|
|||
</a-tooltip>
|
||||
</a-popconfirm> -->
|
||||
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
}"
|
||||
@click="table.openDialog('edit')"
|
||||
>
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:action`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: `${slotProps.status ? '禁用' : '启用'}`,
|
||||
}"
|
||||
:popConfirm="{
|
||||
title: `确定${
|
||||
slotProps.status ? '禁用' : '启用'
|
||||
}吗?`,
|
||||
onConfirm: () => table.changeStatus(slotProps),
|
||||
}"
|
||||
>
|
||||
<stop-outlined v-if="slotProps.status" />
|
||||
<play-circle-outlined v-else />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '重置密码',
|
||||
}"
|
||||
@click="table.openDialog('reset', slotProps)"
|
||||
>
|
||||
<AIcon type="icon-zhongzhimima" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
:tooltip="{
|
||||
title: slotProps.status
|
||||
? '请先禁用,再删除'
|
||||
: '删除',
|
||||
}"
|
||||
:popConfirm="{
|
||||
title: `确认删除`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
:disabled="slotProps.status"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
}"
|
||||
@click="table.openDialog('edit')"
|
||||
>
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:action`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: `${slotProps.status ? '禁用' : '启用'}`,
|
||||
}"
|
||||
:popConfirm="{
|
||||
title: `确定${
|
||||
slotProps.status ? '禁用' : '启用'
|
||||
}吗?`,
|
||||
onConfirm: () => table.changeStatus(slotProps),
|
||||
}"
|
||||
>
|
||||
<stop-outlined v-if="slotProps.status" />
|
||||
<play-circle-outlined v-else />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '重置密码',
|
||||
}"
|
||||
@click="table.openDialog('reset', slotProps)"
|
||||
>
|
||||
<AIcon type="icon-zhongzhimima" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
:tooltip="{
|
||||
title: slotProps.status
|
||||
? '请先禁用,再删除'
|
||||
: '删除',
|
||||
}"
|
||||
:popConfirm="{
|
||||
title: `确认删除`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
:disabled="slotProps.status"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
||||
<div class="dialogs">
|
||||
<EditUserDialog ref="editDialogRef" @confirm="table.refresh" />
|
||||
<div class="dialogs">
|
||||
<EditUserDialog ref="editDialogRef" @confirm="table.refresh" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="UserMange">
|
||||
|
@ -354,8 +358,6 @@ type modalType = '' | 'add' | 'edit' | 'reset';
|
|||
|
||||
<style lang="less" scoped>
|
||||
.user-container {
|
||||
padding: 24px;
|
||||
|
||||
:deep(.ant-table-tbody) {
|
||||
.ant-table-cell {
|
||||
.ant-space-item {
|
||||
|
|
Loading…
Reference in New Issue