Merge branch 'dev' into dev-hub
This commit is contained in:
commit
a77997d87f
|
@ -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"
|
||||
|
|
|
@ -467,3 +467,19 @@ export const saveEdgeMap = (deviceId: string, data?: any) => server.post(`/edge/
|
|||
* @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)
|
|
@ -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),
|
||||
// 解绑
|
||||
|
|
|
@ -18,4 +18,20 @@ export const remove = (id:string) => server.remove(`/alarm/config/${id}`);
|
|||
/**
|
||||
* 手动触发告警
|
||||
*/
|
||||
export const _execute = (data:any) => server.post('/scene/batch/_execute',data)
|
||||
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}`);
|
|
@ -6,3 +6,5 @@ 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 query = (data: any) => server.post('/scene/_query/',data);
|
|
@ -7,3 +7,14 @@ export const getApplyList_api = (data: any) => server.post(`/application/_query/
|
|||
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`);
|
||||
// 获取组织列表
|
||||
export const getAppInfo_api = (id:string) => server.get(`/application/${id}`);
|
||||
// 新增应用
|
||||
export const addApp_api = (data:object) => server.post(`/application`, data);
|
||||
// 更新应用
|
||||
export const updateApp_api = (id:string, data:object) => server.put(`/application/${id}`, data);
|
|
@ -98,7 +98,11 @@ const props = defineProps({
|
|||
class: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
// defaultTerms: {
|
||||
// type: Object,
|
||||
// default: () => ({})
|
||||
// }
|
||||
})
|
||||
|
||||
const searchRef = ref(null)
|
||||
|
@ -223,6 +227,7 @@ const handleParamsFormat = () => {
|
|||
*/
|
||||
const searchSubmit = () => {
|
||||
emit('search', handleParamsFormat())
|
||||
console.log('searchSubmit')
|
||||
if (props.type === 'advanced') {
|
||||
addUrlParams()
|
||||
}
|
||||
|
|
|
@ -144,6 +144,10 @@ const JTable = defineComponent<JTableProps>({
|
|||
pageSize: 12
|
||||
}
|
||||
}
|
||||
},
|
||||
scroll: {
|
||||
type: Object,
|
||||
default: () => { x: 1366 }
|
||||
}
|
||||
} as any,
|
||||
setup(props: JTableProps, { slots, emit, expose }) {
|
||||
|
@ -260,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 }}>
|
||||
|
@ -331,7 +335,7 @@ const JTable = defineComponent<JTableProps>({
|
|||
pagination={false}
|
||||
rowKey="id"
|
||||
rowSelection={props.rowSelection}
|
||||
scroll={{ x: 1366 }}
|
||||
scroll={props.scroll}
|
||||
v-slots={{
|
||||
bodyCell: (dt: Record<string, any>) => {
|
||||
const { column, record } = dt;
|
||||
|
|
|
@ -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}`)
|
||||
}
|
||||
},
|
||||
|
|
|
@ -68,7 +68,7 @@ const defaultOptions = {
|
|||
};
|
||||
|
||||
export const useSceneStore = defineStore('scene', () => {
|
||||
const data = reactive<FormModelType | any>({
|
||||
const data = reactive<FormModelType>({
|
||||
trigger: { type: ''},
|
||||
options: defaultOptions,
|
||||
branches: defaultBranches,
|
||||
|
@ -116,67 +116,3 @@ export const useSceneStore = defineStore('scene', () => {
|
|||
getDetail
|
||||
}
|
||||
})
|
||||
//
|
||||
// export const useSceneStore = defineStore({
|
||||
// id: 'scene',
|
||||
// state: (): DataType => {
|
||||
// return {
|
||||
// data: {
|
||||
// trigger: { type: ''},
|
||||
// options: defaultOptions,
|
||||
// branches: defaultBranches,
|
||||
// description: ''
|
||||
// },
|
||||
// productCache: {}
|
||||
// }
|
||||
// },
|
||||
// actions: {
|
||||
// /**
|
||||
// * 初始化数据
|
||||
// */
|
||||
// initData() {
|
||||
//
|
||||
// },
|
||||
// /**
|
||||
// * 获取详情
|
||||
// * @param id
|
||||
// */
|
||||
// async getDetail(id: string) {
|
||||
// const resp = await detail(id)
|
||||
// if (resp.success) {
|
||||
// const result = resp.result as SceneItem
|
||||
// const triggerType = result.triggerType
|
||||
// let branches: any[] = result.branches
|
||||
//
|
||||
// if (!branches) {
|
||||
// branches = cloneDeep(defaultBranches)
|
||||
// if (triggerType === 'device') {
|
||||
// branches.push(null)
|
||||
// }
|
||||
// } else {
|
||||
// const branchesLength = branches.length;
|
||||
// if (
|
||||
// triggerType === 'device' &&
|
||||
// ((branchesLength === 1 && !!branches[0]?.when?.length) || // 有一组数据并且when有值
|
||||
// (branchesLength > 1 && !branches[branchesLength - 1]?.when?.length)) // 有多组否则数据,并且最后一组when有值
|
||||
// ) {
|
||||
// branches.push(null);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// this.data = {
|
||||
// ...result,
|
||||
// trigger: result.trigger || {},
|
||||
// branches: cloneDeep(assignmentKey(branches)),
|
||||
// options: {...defaultOptions, ...result.options },
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// getProduct() {
|
||||
//
|
||||
// }
|
||||
// },
|
||||
// getters: {
|
||||
//
|
||||
// }
|
||||
// })
|
|
@ -8,3 +8,11 @@ export const isUrl = (path: string): boolean => urlReg.test(path)
|
|||
export const inputReg = /^[a-zA-Z0-9_\-]+$/
|
||||
|
||||
export const isInput = (value: string) => inputReg.test(value)
|
||||
|
||||
// cron 表达式
|
||||
|
||||
export const CronRegEx = new RegExp(
|
||||
'^\\s*($|#|\\w+\\s*=|(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[01]?\\d|2[0-3])(?:(?:-|\\/|\\,)(?:[01]?\\d|2[0-3]))?(?:,(?:[01]?\\d|2[0-3])(?:(?:-|\\/|\\,)(?:[01]?\\d|2[0-3]))?)*)\\s+(\\?|\\*|(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?(?:,(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?)*)\\s+(\\?|\\*|(?:[1-9]|1[012])(?:(?:-|\\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?(?:,(?:[1-9]|1[012])(?:(?:-|\\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?)*|\\?|\\*|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?(?:,(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)*)\\s+(\\?|\\*|(?:[0-6])(?:(?:-|\\/|\\,|#)(?:[0-6]))?(?:L)?(?:,(?:[0-6])(?:(?:-|\\/|\\,|#)(?:[0-6]))?(?:L)?)*|\\?|\\*|(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?(?:,(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?)*)(|\\s)+(\\?|\\*|(?:|\\d{4})(?:(?:-|\\/|\\,)(?:|\\d{4}))?(?:,(?:|\\d{4})(?:(?:-|\\/|\\,)(?:|\\d{4}))?)*))$',
|
||||
);
|
||||
|
||||
export const isCron = (value: string) => CronRegEx.test(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,9 +115,13 @@ const Status = defineComponent({
|
|||
<Badge
|
||||
status="default"
|
||||
text={
|
||||
<span>网络组件已禁用,请先<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
<span>网络组件已禁用,请先
|
||||
<PermissionButton
|
||||
type="link"
|
||||
hasPermission="link/Type:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const res = await startNetwork(
|
||||
unref(gateway)?.channelId,
|
||||
);
|
||||
|
@ -143,14 +139,16 @@ const Status = defineComponent({
|
|||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm></span>
|
||||
启用
|
||||
</PermissionButton>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div >
|
||||
) : (
|
||||
<div>
|
||||
<div class={styles.infoItem}>
|
||||
|
@ -287,9 +285,11 @@ const Status = defineComponent({
|
|||
<Badge
|
||||
status="default"
|
||||
text={<span>设备接入网关已禁用,请先
|
||||
<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
<PermissionButton
|
||||
hasPermission="link/Type:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const resp = await startGateway(unref(device).accessId || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
|
@ -305,10 +305,11 @@ const Status = defineComponent({
|
|||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm>
|
||||
启用
|
||||
</PermissionButton>
|
||||
</span>}
|
||||
/>
|
||||
</div>
|
||||
|
@ -411,9 +412,12 @@ const Status = defineComponent({
|
|||
status="default"
|
||||
text={
|
||||
<span>
|
||||
设备接入网关已禁用,请先<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
设备接入网关已禁用,请先
|
||||
<PermissionButton
|
||||
hasPermission="link/AccessConfig:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const resp = await startGateway(unref(device).accessId || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
|
@ -429,10 +433,11 @@ const Status = defineComponent({
|
|||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm>
|
||||
启用
|
||||
</PermissionButton>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
@ -519,9 +524,12 @@ const Status = defineComponent({
|
|||
status="default"
|
||||
text={
|
||||
<span>
|
||||
网关父设备已禁用,请先<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
网关父设备已禁用,请先
|
||||
<PermissionButton
|
||||
hasPermission="device/Product:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const resp = await _deploy(response?.result?.id || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
|
@ -537,10 +545,11 @@ const Status = defineComponent({
|
|||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm>
|
||||
启用
|
||||
</PermissionButton>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
@ -623,9 +632,12 @@ const Status = defineComponent({
|
|||
status="default"
|
||||
text={
|
||||
<span>
|
||||
产品已禁用,请<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
产品已禁用,请
|
||||
<PermissionButton
|
||||
hasPermission="device/Product:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const resp = await _deployProduct(unref(device).productId || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
|
@ -641,10 +653,11 @@ const Status = defineComponent({
|
|||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm>
|
||||
启用
|
||||
</PermissionButton>
|
||||
产品
|
||||
</span>
|
||||
}
|
||||
|
@ -695,9 +708,12 @@ const Status = defineComponent({
|
|||
status="default"
|
||||
text={
|
||||
<span>
|
||||
设备已禁用,请<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
设备已禁用,请
|
||||
<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: '离线' }
|
||||
|
@ -714,10 +730,12 @@ const Status = defineComponent({
|
|||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<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`
|
||||
})
|
||||
})
|
||||
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>
|
|
@ -1,3 +1,54 @@
|
|||
<!-- 坐标点拾取组件 -->
|
||||
<template>
|
||||
<div>AMap</div>
|
||||
<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>
|
|
@ -1,3 +1,218 @@
|
|||
<template>
|
||||
<div>Charts</div>
|
||||
<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>
|
|
@ -18,6 +18,13 @@
|
|||
<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
|
||||
|
@ -30,7 +37,7 @@
|
|||
@click="_download(record)"
|
||||
><AIcon type="DownloadOutlined"
|
||||
/></a-button>
|
||||
<a-button type="link"
|
||||
<a-button type="link" @click="showDetail(record)"
|
||||
><AIcon type="SearchOutlined"
|
||||
/></a-button>
|
||||
</a-space>
|
||||
|
@ -38,6 +45,28 @@
|
|||
</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>
|
||||
|
@ -46,6 +75,8 @@ 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: {
|
||||
|
@ -57,8 +88,11 @@ const _props = defineProps({
|
|||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
const dataSource = ref({});
|
||||
const current = ref<any>({});
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
const columns = computed(() => {
|
||||
const arr: any[] = [
|
||||
|
@ -92,6 +126,11 @@ const showLoad = computed(() => {
|
|||
);
|
||||
});
|
||||
|
||||
const showDetail = (item: any) => {
|
||||
visible.value = true;
|
||||
current.value = item;
|
||||
};
|
||||
|
||||
const queryPropertyData = async (params: any) => {
|
||||
const resp = await getPropertyData(
|
||||
instanceStore.current.id,
|
||||
|
@ -99,7 +138,7 @@ const queryPropertyData = async (params: any) => {
|
|||
...params,
|
||||
terms: {
|
||||
property: _props.data.id,
|
||||
timestamp$BTW: _props.time?.length ? _props.time : [],
|
||||
timestamp$BTW: _props.time,
|
||||
},
|
||||
sorts: { timestamp: 'desc' },
|
||||
}),
|
||||
|
@ -109,14 +148,20 @@ const queryPropertyData = async (params: any) => {
|
|||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if (_props.data.id) {
|
||||
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({
|
||||
|
@ -141,3 +186,9 @@ const _download = (record: any) => {
|
|||
document.body.removeChild(downNode);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-pagination-item) {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
|
@ -2,15 +2,15 @@
|
|||
<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">
|
||||
<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 />
|
||||
<Charts :data="props.data" :time="_getTimes" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="geo" tab="轨迹">
|
||||
<AMap />
|
||||
<a-tab-pane key="geo" tab="轨迹" v-if="data?.valueType?.type === 'geoPoint'">
|
||||
<PropertyAMap :data="props.data" :time="_getTimes" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
|
@ -21,7 +21,7 @@
|
|||
import type { Dayjs } from 'dayjs';
|
||||
import TimeComponent from './TimeComponent.vue'
|
||||
import Charts from './Charts.vue'
|
||||
import AMap from './AMap.vue'
|
||||
import PropertyAMap from './PropertyAMap.vue'
|
||||
import Table from './Table.vue'
|
||||
|
||||
const props = defineProps({
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
<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,9 +17,27 @@
|
|||
: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>
|
||||
<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>
|
||||
|
@ -27,8 +45,12 @@
|
|||
<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>
|
||||
<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> -->
|
||||
|
@ -36,7 +58,7 @@
|
|||
</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);
|
||||
|
|
|
@ -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,7 +53,7 @@
|
|||
import { getImage } from "@/utils/comm";
|
||||
import { message } from "ant-design-vue";
|
||||
import ValueDetail from './ValueDetail.vue'
|
||||
import {getType, imgMap} from './index'
|
||||
import {getType, imgMap, imgList, videoList, fileList} from './index'
|
||||
|
||||
const _data = defineProps({
|
||||
data: {
|
||||
|
@ -115,7 +115,6 @@ const getDetail = (_type: string) => {
|
|||
_types.value = flag
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -32,11 +32,8 @@
|
|||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps)"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<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"
|
||||
|
@ -46,6 +43,19 @@
|
|||
<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'"
|
||||
>
|
||||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #paginationRender>
|
||||
|
@ -76,7 +86,11 @@
|
|||
@close="indicatorVisible = false"
|
||||
:data="currentInfo"
|
||||
/>
|
||||
<Detail v-if="detailVisible" :data="currentInfo" @close="detailVisible = false" />
|
||||
<Detail
|
||||
v-if="detailVisible"
|
||||
:data="currentInfo"
|
||||
@close="detailVisible = false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -240,10 +254,14 @@ 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)
|
||||
unref(list)
|
||||
.sort((a: any, b: any) => a.timestamp - b.timestamp)
|
||||
.forEach((item: any) => {
|
||||
const { value } = item;
|
||||
propertyValue.value[value?.property] = { ...item, ...value };
|
||||
propertyValue.value[value?.property] = {
|
||||
...item,
|
||||
...value,
|
||||
};
|
||||
});
|
||||
// list.value = [...list.value, payload];
|
||||
// throttle(valueChange(list.value), 500);
|
||||
|
@ -337,8 +355,8 @@ const onSearch = () => {
|
|||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
subRef.value && subRef.value?.unsubscribe()
|
||||
})
|
||||
subRef.value && subRef.value?.unsubscribe();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<page-container
|
||||
:tabList="list"
|
||||
@back="onBack"
|
||||
:tabActiveKey="instanceStore.active"
|
||||
:tabActiveKey="instanceStore.tabActiveKey"
|
||||
@tabChange="onTabChange"
|
||||
>
|
||||
<template #title>
|
||||
|
|
|
@ -155,13 +155,12 @@
|
|||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3
|
||||
class="card-item-content-title"
|
||||
@click.stop="handleView(slotProps.id)"
|
||||
>
|
||||
<Ellipsis style="width: calc(100% - 100px)">
|
||||
<span style="font-size: 16px; font-weight: 600" @click.stop="handleView(slotProps.id)">
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
<a-row>
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<a-row style="margin-top: 20px">
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
|
@ -172,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>
|
||||
|
|
|
@ -48,22 +48,40 @@
|
|||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img :src="getImage('/device-product.png')" />
|
||||
<img
|
||||
:src="
|
||||
slotProps.photoUrl ||
|
||||
getImage('/device-product.png')
|
||||
"
|
||||
class="productImg"
|
||||
/>
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3
|
||||
<Ellipsis
|
||||
><span
|
||||
@click.stop="handleView(slotProps.id)"
|
||||
style="font-weight: 600"
|
||||
style="font-weight: 600; font-size: 16px"
|
||||
>
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
</span></Ellipsis
|
||||
>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
</div>
|
||||
<div>直连设备</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-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
@ -156,12 +174,7 @@
|
|||
</template>
|
||||
</JTable>
|
||||
<!-- 新增、编辑 -->
|
||||
<Save
|
||||
ref="saveRef"
|
||||
:isAdd="isAdd"
|
||||
:title="title"
|
||||
@success="refresh"
|
||||
/>
|
||||
<Save ref="saveRef" :isAdd="isAdd" :title="title" @success="refresh" />
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
|
@ -193,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>('');
|
||||
|
@ -425,7 +438,7 @@ const beforeUpload = (file: any) => {
|
|||
* 查看
|
||||
*/
|
||||
const handleView = (id: string) => {
|
||||
menuStory.jumpPage('device/Product/Detail',{id})
|
||||
menuStory.jumpPage('device/Product/Detail', { id });
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -643,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>
|
||||
|
|
|
@ -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', {
|
||||
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', {
|
||||
menuStory.jumpPage(
|
||||
'media/Device/Channel',
|
||||
{},
|
||||
{
|
||||
id: data.id,
|
||||
type: data.provider,
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -333,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();
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -421,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);
|
||||
|
@ -444,7 +424,7 @@ const handleTypeChange = () => {
|
|||
setTimeout(() => {
|
||||
formData.value.configuration =
|
||||
CONFIG_FIELD_MAP[formData.value.type][formData.value.provider];
|
||||
// resetPublicFiles();
|
||||
resetPublicFiles();
|
||||
}, 0);
|
||||
};
|
||||
|
||||
|
@ -454,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,
|
||||
(f: any) => f.thirdPartyUserId === deptUser.id,
|
||||
);
|
||||
// 平台用户
|
||||
const allUser = allUsers.find(
|
||||
(f: any) => f.id === bindUser?.userId,
|
||||
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"
|
||||
|
@ -800,26 +801,59 @@ const formData = ref<TemplateFormData>({
|
|||
});
|
||||
|
||||
/**
|
||||
* 重置公用字段值
|
||||
* 重置字段值
|
||||
*/
|
||||
const resetPublicFiles = () => {
|
||||
switch (formData.value.provider) {
|
||||
case 'dingTalkMessage':
|
||||
formData.value.template.agentId = '';
|
||||
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;
|
||||
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();
|
||||
};
|
||||
|
||||
// 根据通知方式展示对应的字段
|
||||
|
@ -831,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();
|
||||
|
@ -848,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: '请选择通知方式' }],
|
||||
|
@ -917,7 +935,7 @@ watch(
|
|||
() => formData.value.template.markdown?.title,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
variableReg(val);
|
||||
variableReg();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
@ -926,7 +944,7 @@ watch(
|
|||
() => formData.value.template.link?.title,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
variableReg(val);
|
||||
variableReg();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
@ -935,7 +953,7 @@ watch(
|
|||
() => formData.value.template.subject,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
variableReg(val);
|
||||
variableReg();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
@ -945,7 +963,7 @@ watch(
|
|||
() => formData.value.template.message,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
variableReg(val);
|
||||
variableReg();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
@ -954,21 +972,42 @@ watch(
|
|||
() => formData.value.template.body,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
variableReg(val);
|
||||
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 = (value: string) => {
|
||||
const variableReg = () => {
|
||||
const _val = spliceStr();
|
||||
// 已经存在的变量
|
||||
const oldKey = formData.value.variableDefinitions?.map((m) => m.id);
|
||||
// 正则提取${}里面的值
|
||||
const pattern = /(?<=\$\{).*?(?=\})/g;
|
||||
const titleList = value.match(pattern)?.filter((f) => f);
|
||||
const titleList = _val.match(pattern)?.filter((f) => f);
|
||||
const newKey = [...new Set(titleList)];
|
||||
const result = newKey?.map((m) =>
|
||||
oldKey.includes(m)
|
||||
|
@ -980,28 +1019,37 @@ const variableReg = (value: string) => {
|
|||
format: '%s',
|
||||
},
|
||||
);
|
||||
formData.value.variableDefinitions = [
|
||||
...new Set([
|
||||
...formData.value.variableDefinitions,
|
||||
...(result as IVariableDefinitions[]),
|
||||
]),
|
||||
];
|
||||
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 = '';
|
||||
if (formData.value.template.link) {
|
||||
formData.value.template.link.title = '';
|
||||
formData.value.template.link.picUrl = '';
|
||||
formData.value.template.link.messageUrl = '';
|
||||
}
|
||||
if (formData.value.template.markdown) {
|
||||
formData.value.template.markdown.title = '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1037,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);
|
||||
};
|
||||
|
@ -1047,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();
|
||||
};
|
||||
|
@ -1112,6 +1163,7 @@ const handleSubmit = () => {
|
|||
setTimeout(() => {
|
||||
validate()
|
||||
.then(async () => {
|
||||
if (formData.value.provider === 'ttsCode')
|
||||
formData.value.template.ttsCode =
|
||||
formData.value.template.templateCode;
|
||||
btnLoading.value = true;
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<a-button>导入</a-button>
|
||||
</a-upload>
|
||||
<a-popconfirm
|
||||
title="确认导出当前页数据?"
|
||||
title="确认导出?"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="handleExport"
|
||||
|
@ -314,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>
|
|
@ -2,7 +2,7 @@
|
|||
<page-container>
|
||||
<div>
|
||||
<Search
|
||||
:columns="query.columns"
|
||||
:columns="columns"
|
||||
target="device-instance"
|
||||
@search="handleSearch"
|
||||
></Search>
|
||||
|
@ -13,6 +13,7 @@
|
|||
:defaultParams="{
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
}"
|
||||
:params="params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
|
@ -41,23 +42,27 @@
|
|||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3 style="font-weight: 600">
|
||||
<Ellipsis>
|
||||
<span style="font-weight: 600; font-size: 16px">
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="content-des-title">
|
||||
关联场景联动
|
||||
</div>
|
||||
<div class="rule-desc">
|
||||
<Ellipsis
|
||||
><div>
|
||||
{{ (slotProps?.scene || []).map((item: any) => item?.name).join(',') || '' }}
|
||||
</div>
|
||||
</div></Ellipsis
|
||||
>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div class="content-des-title">
|
||||
告警级别
|
||||
</div>
|
||||
<div class="rule-desc">
|
||||
<div>
|
||||
{{ (Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title ||
|
||||
slotProps.level }}
|
||||
</div>
|
||||
|
@ -200,67 +205,22 @@ import {
|
|||
_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',
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'targetType',
|
||||
key: 'targetType',
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '告警级别',
|
||||
dataIndex: 'level',
|
||||
key: 'level',
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '关联场景联动',
|
||||
dataIndex: 'sceneId',
|
||||
wdith: 250,
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
const map = {
|
||||
product: '产品',
|
||||
device: '设备',
|
||||
org: '组织',
|
||||
other: '其他',
|
||||
};
|
||||
const query = {
|
||||
columns: [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
|
@ -269,10 +229,79 @@ const query = {
|
|||
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: [
|
||||
|
@ -289,13 +318,25 @@ const query = {
|
|||
},
|
||||
{
|
||||
title: '说明',
|
||||
key: 'description',
|
||||
dataIndex: 'description',
|
||||
search: {
|
||||
type: 'string',
|
||||
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;
|
||||
|
@ -355,9 +396,7 @@ const getActions = (
|
|||
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
title.value = '编辑';
|
||||
isAdd.value = 2;
|
||||
nextTick(() => {});
|
||||
menuStory.jumpPage('rule-engine/Alarm/Configuration/Save',{},{id:data.id});
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -421,14 +460,12 @@ const getActions = (
|
|||
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;
|
||||
}
|
||||
.rule-desc {
|
||||
white-space: nowrap; /*强制在同一行内显示所有文本,直到文本结束或者遭遇br标签对象才换行。*/
|
||||
overflow: hidden; /*超出部分隐藏*/
|
||||
text-overflow: ellipsis; /*隐藏部分以省略号代替*/
|
||||
}
|
||||
</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">
|
||||
<Ellipsis>
|
||||
<span style="font-weight: 600; font-size: 16px">
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="rule-desc">
|
||||
<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';
|
||||
|
@ -266,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 () => {
|
||||
|
@ -332,9 +348,4 @@ const handleSearch = (e: any) => {
|
|||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.rule-desc {
|
||||
white-space: nowrap; /*强制在同一行内显示所有文本,直到文本结束或者遭遇br标签对象才换行。*/
|
||||
overflow: hidden; /*超出部分隐藏*/
|
||||
text-overflow: ellipsis; /*隐藏部分以省略号代替*/
|
||||
}
|
||||
</style>
|
|
@ -6,7 +6,7 @@
|
|||
@click='save'
|
||||
@cancel='cancel'
|
||||
>
|
||||
<a-steps :current='addModel.stepNumber'>
|
||||
<a-steps :current='addModel.stepNumber' @change='stepChange'>
|
||||
<a-step>
|
||||
<template #title>选择产品</template>
|
||||
</a-step>
|
||||
|
@ -17,19 +17,28 @@
|
|||
<template #title>触发类型</template>
|
||||
</a-step>
|
||||
</a-steps>
|
||||
<a-divider style='margin-bottom: 0px' />
|
||||
<div class='steps-content'>
|
||||
<Product :rowKey='addModel.productId' />
|
||||
<Product v-if='addModel.stepNumber === 0' v-model:rowKey='addModel.productId' v-model:detail='addModel.productDetail' />
|
||||
<DeviceSelect
|
||||
v-else-if='addModel.stepNumber === 1'
|
||||
:productId='addModel.productId'
|
||||
v-model:deviceKeys='addModel.deviceKeys'
|
||||
v-model:orgId='addModel.orgId'
|
||||
v-model:selector='addModel.selector'
|
||||
v-model:selectorValues='addModel.selectorValues'
|
||||
/>
|
||||
<Type
|
||||
v-else-if='addModel.stepNumber === 2'
|
||||
:metadata='addModel.metadata'
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class='steps-action'>
|
||||
<template>
|
||||
<a-button v-if='addModel.stepNumber === 0' @click='cancel'>取消</a-button>
|
||||
<a-button v-else>上一步</a-button>
|
||||
</template>
|
||||
<template>
|
||||
<a-button type='primary' v-if='addModel.stepNumber < 2'>下一步</a-button>
|
||||
<a-button type='primary' v-else>确定</a-button>
|
||||
</template>
|
||||
<a-button v-else @click='prev'>上一步</a-button>
|
||||
<a-button type='primary' v-if='addModel.stepNumber < 2' @click='saveClick'>下一步</a-button>
|
||||
<a-button type='primary' v-else @click='saveClick'>确定</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
@ -37,10 +46,12 @@
|
|||
|
||||
<script setup lang='ts' name='AddModel'>
|
||||
import type { PropType } from 'vue'
|
||||
import { TriggerDevice } from '@/views/rule-engine/Scene/typings'
|
||||
import type { metadataType, TriggerDevice } from '@/views/rule-engine/Scene/typings'
|
||||
import { onlyMessage } from '@/utils/comm'
|
||||
import { detail as deviceDetail } from '@/api/device/instance'
|
||||
import Product from './Product.vue'
|
||||
import DeviceSelect from './DeviceSelect.vue'
|
||||
import Type from './Type.vue'
|
||||
|
||||
type Emit = {
|
||||
(e: 'cancel'): void
|
||||
|
@ -54,11 +65,7 @@ interface AddModelType extends Omit<TriggerDevice, 'selectorValues'> {
|
|||
orgId: Array<{ label: string, value: string }>
|
||||
productDetail: any
|
||||
selectorValues: Array<{ label: string, value: string }>
|
||||
metadata: {
|
||||
properties?: any[]
|
||||
functions?: any[]
|
||||
events?: any[]
|
||||
}
|
||||
metadata: metadataType
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
@ -97,39 +104,56 @@ const handleOptions = () => {
|
|||
|
||||
}
|
||||
|
||||
const prev = () => {
|
||||
addModel.stepNumber = addModel.stepNumber - 1
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
emit("cancel")
|
||||
}
|
||||
|
||||
const handleMetadata = (metadata: string) => {
|
||||
const handleMetadata = (metadata?: string) => {
|
||||
try {
|
||||
addModel.metadata = JSON.parse(metadata)
|
||||
addModel.metadata = JSON.parse(metadata || "{}")
|
||||
} catch (e) {
|
||||
console.warn('handleMetadata: ' + e)
|
||||
}
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
if (addModel.stepNumber === 0) {
|
||||
const save = async (step?: number) => {
|
||||
let _step = step !== undefined ? step : addModel.stepNumber
|
||||
if (_step === 0) {
|
||||
addModel.productId ? addModel.stepNumber = 1 : onlyMessage('请选择产品', 'error')
|
||||
} else if (addModel.stepNumber === 1) {
|
||||
} else if (_step === 1) {
|
||||
const isFixed = addModel.selector === 'fixed' // 是否选择方式为设备
|
||||
if ((['fixed', 'org'].includes(addModel.selector) ) && addModel.selectorValues?.length) {
|
||||
if ((['fixed', 'org'].includes(addModel.selector) ) && !addModel.selectorValues?.length) {
|
||||
return onlyMessage(isFixed ? '请选择设备' : '请选择部门', 'error')
|
||||
}
|
||||
// 选择方式为设备且仅选中一个设备时,物模型取该设备
|
||||
if (isFixed && addModel.selectorValues?.length === 1) {
|
||||
const resp = await deviceDetail(addModel.selectorValues[0].value)
|
||||
addModel.metadata
|
||||
handleMetadata(resp.result.metadata)
|
||||
} else {
|
||||
handleMetadata(addModel.productDetail?.metadata)
|
||||
}
|
||||
addModel.stepNumber = 2
|
||||
} else {
|
||||
|
||||
}
|
||||
//
|
||||
}
|
||||
// handleOptions()
|
||||
// emit('update:value', {})
|
||||
}
|
||||
|
||||
const saveClick = () => save()
|
||||
|
||||
const stepChange = (step: number) => {
|
||||
if (step !== 0) {
|
||||
save(step - 1)
|
||||
} else {
|
||||
addModel.stepNumber = 0
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
<template>
|
||||
<Search
|
||||
:columns="columns"
|
||||
type='simple'
|
||||
@search="handleSearch"
|
||||
class='search'
|
||||
target="scene-triggrt-device-device"
|
||||
/>
|
||||
<a-divider style='margin: 0' />
|
||||
<j-table
|
||||
ref='actionRef'
|
||||
model='CARD'
|
||||
:columns='columns'
|
||||
:request='deviceQuery'
|
||||
:gridColumn='2'
|
||||
:params='params'
|
||||
:bodyStyle='{
|
||||
paddingRight: 0,
|
||||
paddingLeft: 0
|
||||
}'
|
||||
>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value='slotProps'
|
||||
:active="deviceRowKeys.includes(slotProps.id)"
|
||||
:status="slotProps.state?.value"
|
||||
:statusText="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
online: 'success',
|
||||
offline: 'error',
|
||||
notActive: 'warning',
|
||||
}"
|
||||
@click="handleClick"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img width='88' height='88' :src="slotProps.photoUrl || getImage('/device/instance/device-card.png')" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis style='width: calc(100% - 100px)'>
|
||||
<span style="font-size: 16px;font-weight: 600" >
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
</div>
|
||||
<div>{{ slotProps.deviceType?.text }}</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
产品名称
|
||||
</div>
|
||||
<div>{{ slotProps.productName }}</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
</j-table>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='DeviceSelectList'>
|
||||
import type { PropType } from 'vue'
|
||||
import { getImage } from '@/utils/comm'
|
||||
import { query } from '@/api/device/instance'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
type Emit = {
|
||||
(e: 'update', data: Array<{ name: string, value: string}>): void
|
||||
}
|
||||
|
||||
const actionRef = ref()
|
||||
const params = ref({})
|
||||
const context = inject('SceneDeviceAddModel')
|
||||
const props = defineProps({
|
||||
rowKeys: {
|
||||
type: Array as PropType<Array<{ name: string, value: string}>>,
|
||||
default: () => ([])
|
||||
},
|
||||
productId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const deviceRowKeys = computed(() => {
|
||||
return props.rowKeys.map(item => item.value)
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
width: 300,
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
search: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '设备名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
first: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
width: 200,
|
||||
search: {
|
||||
type: 'date'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
width: 90,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '禁用', value: 'notActive' },
|
||||
{ label: '离线', value: 'offline' },
|
||||
{ label: '在线', value: 'online' },
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
const handleSearch = (p: any) => {
|
||||
params.value = p
|
||||
}
|
||||
|
||||
const deviceQuery = (p: any) => {
|
||||
const sorts: any = [];
|
||||
|
||||
if (props.rowKeys) {
|
||||
props.rowKeys.forEach(rowKey => {
|
||||
sorts.push({
|
||||
name: 'id',
|
||||
value: rowKey,
|
||||
});
|
||||
})
|
||||
}
|
||||
sorts.push({ name: 'createTime', order: 'desc' });
|
||||
const terms = [
|
||||
...p.terms,
|
||||
{ terms: [{ column: "productId", value: props.productId }]}
|
||||
]
|
||||
return query({ ...p, terms, sorts })
|
||||
}
|
||||
|
||||
const handleClick = (detail: any) => {
|
||||
const cloneRowKeys = cloneDeep(props.rowKeys)
|
||||
const indexOf = cloneRowKeys.findIndex(item => item.value === detail.id)
|
||||
if (indexOf !== -1) {
|
||||
cloneRowKeys.splice(indexOf, 1)
|
||||
} else {
|
||||
cloneRowKeys.push({
|
||||
name: detail.name,
|
||||
value: detail.id
|
||||
})
|
||||
}
|
||||
console.log('cloneRowKeys', cloneRowKeys)
|
||||
emit('update', cloneRowKeys)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search {
|
||||
margin-bottom: 0;
|
||||
padding-right: 0px;
|
||||
padding-left: 0px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<div class='device-select'>
|
||||
<TopCard :options='typeList' v-model:value='selectorModel' @select='select' />
|
||||
<DeviceList v-if='selectorModel === "fixed"' :productId='productId' :row-keys='devices' @update='updateDevice' />
|
||||
<OrgList v-else-if='selectorModel === "org"' :productId='productId' :row-keys='orgIds' @update='updateOrg' />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts'>
|
||||
import TopCard from '@/views/rule-engine/Scene/Save/components/TopCard.vue'
|
||||
import DeviceList from './DeviceList.vue'
|
||||
import OrgList from './OrgList.vue'
|
||||
import { getImage } from '@/utils/comm'
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
type ItemType = {
|
||||
name: string,
|
||||
value: string
|
||||
}
|
||||
|
||||
type Emit = {
|
||||
(e: 'update:selector', data: string): void
|
||||
(e: 'update:selectorValues', data: ItemType[]): void
|
||||
(e: 'update:deviceKeys', data: ItemType[]): void
|
||||
(e: 'update:orgId', data: ItemType[]): void
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const props = defineProps({
|
||||
productId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
selector: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
device: {
|
||||
type: Array as PropType<ItemType[]>,
|
||||
default: () => []
|
||||
},
|
||||
orgId: {
|
||||
type: Array as PropType<ItemType[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const selectorModel = ref(props.selector)
|
||||
const devices = ref(props.device)
|
||||
const orgIds = ref(props.orgId)
|
||||
|
||||
const typeList = [
|
||||
{ label: '自定义', value: 'fixed', tip: '自定义选择当前产品下的任意设备', img: getImage('/scene/device-custom.png')},
|
||||
{ label: '全部', value: 'all', tip: '产品下的所有设备', img: getImage('/scene/trigger-device-all.png')},
|
||||
{ label: '按组织', value: 'org', tip: '选择产品下归属于具体组织的设备', img: getImage('/scene/trigger-device-org.png')},
|
||||
]
|
||||
|
||||
const select = (s: string) => {
|
||||
selectorModel.value = s
|
||||
emit('update:selector', s)
|
||||
emit('update:selectorValues', [])
|
||||
}
|
||||
|
||||
const updateDevice = (d: any[]) => {
|
||||
devices.value = d
|
||||
emit('update:deviceKeys', d)
|
||||
emit('update:selectorValues', d)
|
||||
}
|
||||
|
||||
const updateOrg = (d: any[]) => {
|
||||
orgIds.value = d
|
||||
emit('update:orgId', d)
|
||||
emit('update:selectorValues', d)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
.device-select{
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,130 @@
|
|||
<template>
|
||||
<Search
|
||||
:columns="columns"
|
||||
type='simple'
|
||||
@search="handleSearch"
|
||||
class='search'
|
||||
target="scene-triggrt-device-category"
|
||||
/>
|
||||
<a-divider style='margin: 0' />
|
||||
<JTable
|
||||
ref="instanceRef"
|
||||
model='TABLE'
|
||||
type='TREE'
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
:scroll="{
|
||||
y: 350
|
||||
}"
|
||||
:expandable='{
|
||||
expandedRowKeys: openKeys,
|
||||
onExpandedRowsChange: expandedRowChange,
|
||||
}'
|
||||
:rowSelection='{
|
||||
type: "radio",
|
||||
selectedRowKeys: orgRowKeys,
|
||||
onChange: selectedRowChange
|
||||
}'
|
||||
:onChange='tableChange'
|
||||
>
|
||||
|
||||
</JTable>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='OrgList'>
|
||||
import type { PropType } from 'vue'
|
||||
import { getExpandedRowById } from './util'
|
||||
import { getTreeData_api } from '@/api/system/department'
|
||||
|
||||
type Emit = {
|
||||
(e: 'update', data: Array<{ name: string, value: string}>): void
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
rowKeys: {
|
||||
type: Array as PropType<Array<{ name: string, value: string}>>,
|
||||
default: () => ([])
|
||||
},
|
||||
productId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const params = ref()
|
||||
const openKeys = ref<string[]>([])
|
||||
const selectedRowKeys = ref(props.rowKeys.map(item => item.value))
|
||||
const sortParam = ref<{ name:string, order: string }>({ name: 'sortIndex', order: 'asc' })
|
||||
const iniPage = ref(true)
|
||||
|
||||
const orgRowKeys = computed(() => {
|
||||
return props.rowKeys.map(item => item.value)
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
width: 300,
|
||||
ellipsis: true,
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sortIndex',
|
||||
sorter: true,
|
||||
},
|
||||
]
|
||||
|
||||
const handleSearch = (p: any) => {
|
||||
params.value = p
|
||||
}
|
||||
|
||||
const tableChange = (_: any, f: any, sorter: any) => {
|
||||
if (sorter.order) {
|
||||
sortParam.value = { name: sorter.columnKey, order: (sorter.order as string).replace('end', ''), }
|
||||
} else {
|
||||
sortParam.value = { name: 'sortIndex', order: 'asc' }
|
||||
}
|
||||
}
|
||||
|
||||
const query = async (p: any) => {
|
||||
const _params: any = {
|
||||
paging: false,
|
||||
sorts: [sortParam.value],
|
||||
}
|
||||
|
||||
if (p.terms && p.terms.length) {
|
||||
_params.terms = p.terms
|
||||
}
|
||||
|
||||
const resp = await getTreeData_api(_params)
|
||||
|
||||
if (iniPage.value && props.rowKeys.length) {
|
||||
iniPage.value = false
|
||||
openKeys.value = getExpandedRowById(props.rowKeys[0]?.value, resp.result as any[])
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
const selectedRowChange = (_: any, selectedRows: any[]) => {
|
||||
const item = selectedRows[0]
|
||||
console.log(selectedRows)
|
||||
emit('update', item ? [{ name: item.name, value: item.id }] : [])
|
||||
}
|
||||
|
||||
const expandedRowChange = (keys: string[]) => {
|
||||
openKeys.value = keys
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search {
|
||||
margin-bottom: 0;
|
||||
padding-right: 0px;
|
||||
padding-left: 0px;
|
||||
}
|
||||
</style>
|
|
@ -4,18 +4,25 @@
|
|||
type='simple'
|
||||
@search="handleSearch"
|
||||
class='search'
|
||||
target="scene-triggrt-device-device"
|
||||
/>
|
||||
<a-divider style='margin: 0' />
|
||||
<j-table
|
||||
:columns='columns'
|
||||
ref='actionRef'
|
||||
model='CARD'
|
||||
:columns='columns'
|
||||
:params='params'
|
||||
:request='productQuery'
|
||||
:gridColumn='2'
|
||||
model='CARD'
|
||||
:bodyStyle='{
|
||||
paddingRight: 0,
|
||||
paddingLeft: 0
|
||||
}'
|
||||
>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value='slotProps'
|
||||
:active="selectedRowKeys.includes(slotProps.id)"
|
||||
:active="rowKey === slotProps.id"
|
||||
:status="slotProps.state"
|
||||
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
||||
:statusNames="{ 1: 'success', 0: 'error', }"
|
||||
|
@ -23,13 +30,17 @@
|
|||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img :src="getImage('/device-product.png')" />
|
||||
<img width='88' height='88' :src="slotProps.photoUrl || getImage('/device-product.png')" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3 style="font-weight: 600" >
|
||||
<div style='width: calc(100% - 100px)'>
|
||||
<Ellipsis>
|
||||
<span style="font-size: 16px;font-weight: 600" >
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
</span>
|
||||
</Ellipsis>
|
||||
</div>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
|
@ -51,16 +62,25 @@ import { getTreeData_api } from '@/api/system/department'
|
|||
import { isNoCommunity } from '@/utils/utils'
|
||||
import { getImage } from '@/utils/comm'
|
||||
|
||||
type Emit = {
|
||||
(e: 'update:rowKey', data: string): void
|
||||
(e: 'update:detail', data: string): void
|
||||
}
|
||||
|
||||
const actionRef = ref()
|
||||
const params = ref({})
|
||||
const props = defineProps({
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
detail: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const selectedRowKeys = ref(props.rowKey)
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const columns = [
|
||||
{
|
||||
|
@ -69,12 +89,19 @@ const columns = [
|
|||
width: 300,
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
first: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '网关类型',
|
||||
|
@ -199,7 +226,6 @@ const columns = [
|
|||
|
||||
const handleSearch = (p: any) => {
|
||||
params.value = p
|
||||
actionRef.value.required()
|
||||
}
|
||||
|
||||
const productQuery = (p: any) => {
|
||||
|
@ -217,12 +243,8 @@ const productQuery = (p: any) => {
|
|||
}
|
||||
|
||||
const handleClick = (detail: any) => {
|
||||
const _selected = new Set(selectedRowKeys.value)
|
||||
if (_selected.has(detail.id)) {
|
||||
_selected.delete(detail.id)
|
||||
} else {
|
||||
_selected.add(detail.id)
|
||||
}
|
||||
emit('update:rowKey', detail.id)
|
||||
emit('update:detail', detail)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
@ -230,5 +252,7 @@ const handleClick = (detail: any) => {
|
|||
<style scoped lang='less'>
|
||||
.search {
|
||||
margin-bottom: 0;
|
||||
padding-right: 0px;
|
||||
padding-left: 0px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,100 @@
|
|||
<template>
|
||||
<div class='type'>
|
||||
<a-form ref='typeForm' :model='formModel' layout='vertical' :colon='false'>
|
||||
<a-form-item
|
||||
required
|
||||
label='触发类型'
|
||||
>
|
||||
<TopCard
|
||||
:label-bottom='true'
|
||||
:options='options'
|
||||
v-model:value='formModel.operator'
|
||||
/>
|
||||
</a-form-item>
|
||||
<Timer v-if='showTimer' />
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts'>
|
||||
|
||||
import TopCard from '@/views/rule-engine/Scene/Save/components/TopCard.vue'
|
||||
import { getImage } from '@/utils/comm'
|
||||
import { metadataType } from '@/views/rule-engine/Scene/typings'
|
||||
import type { PropType } from 'vue'
|
||||
import { TypeEnum } from '@/views/rule-engine/Scene/Save/Device/util'
|
||||
import Timer from '../components/Timer.vue'
|
||||
|
||||
const props = defineProps({
|
||||
metadata: {
|
||||
type: Object as PropType<metadataType>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const formModel = reactive({
|
||||
operator: 'online',
|
||||
})
|
||||
|
||||
const readProperties = ref<any[]>([])
|
||||
const writeProperties = ref<any[]>([])
|
||||
|
||||
const options = computed(() => {
|
||||
const baseOptions = [
|
||||
{
|
||||
label: '设备上线',
|
||||
value: 'online',
|
||||
img: getImage('/scene/online.png'),
|
||||
},
|
||||
{
|
||||
label: '设备离线',
|
||||
value: 'offline',
|
||||
img: getImage('/scene/offline.png'),
|
||||
},
|
||||
]
|
||||
|
||||
if (props.metadata.events?.length) {
|
||||
baseOptions.push(TypeEnum.reportEvent)
|
||||
}
|
||||
|
||||
if (props.metadata.properties?.length) {
|
||||
const _properties = props.metadata.properties
|
||||
readProperties.value = _properties.filter((item: any) => item.expands.type?.includes('read'))
|
||||
writeProperties.value = _properties.filter((item: any) => item.expands.type?.includes('write'))
|
||||
const reportProperties = _properties.filter((item: any) => item.expands.type?.includes('report'))
|
||||
|
||||
if (readProperties.value.length) {
|
||||
baseOptions.push(TypeEnum.readProperty)
|
||||
}
|
||||
|
||||
if (writeProperties.value.length) {
|
||||
baseOptions.push(TypeEnum.writeProperty)
|
||||
}
|
||||
|
||||
if (reportProperties.length) {
|
||||
baseOptions.push(TypeEnum.reportProperty)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (props.metadata.functions?.length) {
|
||||
baseOptions.push(TypeEnum.invokeFunction)
|
||||
}
|
||||
|
||||
return baseOptions
|
||||
})
|
||||
|
||||
const showTimer = computed(() => {
|
||||
return ['readProperty', 'writeProperty', 'invokeFunction'].includes(formModel.operator)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
.type {
|
||||
max-height: calc(100vh - 350px);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
|
@ -26,7 +26,8 @@ import AddButton from '../components/AddButton.vue'
|
|||
import Title from '../components/Title.vue'
|
||||
|
||||
const sceneStore = useSceneStore()
|
||||
const { data } = storeToRefs(sceneStore)
|
||||
const { data } = storeToRefs<any>(sceneStore)
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
const rules = [{
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import { getImage } from '@/utils/comm'
|
||||
|
||||
export const TypeName = {
|
||||
online: '设备上线',
|
||||
offline: '设备离线',
|
||||
reportEvent: '事件上报',
|
||||
reportProperty: '属性上报',
|
||||
readProperty: '读取属性',
|
||||
writeProperty: '修改属性',
|
||||
invokeFunction: '功能调用',
|
||||
};
|
||||
|
||||
export const TypeEnum = {
|
||||
reportProperty: {
|
||||
label: '属性上报',
|
||||
value: 'reportProperty',
|
||||
img: getImage('/scene/reportProperty.png'),
|
||||
},
|
||||
reportEvent: {
|
||||
label: '事件上报',
|
||||
value: 'reportEvent',
|
||||
img: getImage('/scene/reportProperty.png'),
|
||||
},
|
||||
readProperty: {
|
||||
label: '读取属性',
|
||||
value: 'readProperty',
|
||||
img: getImage('/scene/readProperty.png'),
|
||||
},
|
||||
writeProperty: {
|
||||
label: '修改属性',
|
||||
value: 'writeProperty',
|
||||
img: getImage('/scene/writeProperty.png'),
|
||||
},
|
||||
invokeFunction: {
|
||||
label: '功能调用',
|
||||
value: 'invokeFunction',
|
||||
img: getImage('/scene/invokeFunction.png'),
|
||||
},
|
||||
};
|
||||
|
||||
export const getExpandedRowById = (id: string, data: any[]): string[] => {
|
||||
const expandedKeys:string[] = []
|
||||
const dataMap = new Map()
|
||||
|
||||
const flatMapData = (flatData: any[]) => {
|
||||
flatData.forEach(item => {
|
||||
dataMap.set(item.id, { pid: item.parentId })
|
||||
if (item.children && item.children.length) {
|
||||
flatMapData(item.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getExp = (_id: string) => {
|
||||
const item = dataMap.get(_id)
|
||||
if (item) {
|
||||
expandedKeys.push(_id)
|
||||
if (dataMap.has(dataMap)) {
|
||||
getExp(item.pid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flatMapData(data)
|
||||
|
||||
getExp(id)
|
||||
|
||||
return expandedKeys
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<a-form
|
||||
ref='timerForm'
|
||||
:model='formModel'
|
||||
layout='vertical'
|
||||
:colon='false'
|
||||
>
|
||||
<a-form-item name='trigger'>
|
||||
<a-radio-group
|
||||
v-model:value='formModel.trigger'
|
||||
:options='[
|
||||
{ label: "按周", value: "week" },
|
||||
{ label: "按月", value: "month" },
|
||||
{ label: "cron表达式", value: "cron" },
|
||||
]'
|
||||
option-type='button'
|
||||
button-style='solid'
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if='showCron' name='cron'>
|
||||
<a-input placeholder='corn表达式' v-model='formModel.cron' />
|
||||
</a-form-item>
|
||||
<template v-else>
|
||||
<a-form-item name='when'>
|
||||
|
||||
</a-form-item>
|
||||
<a-form-item name='mod'>
|
||||
<a-radio-group
|
||||
v-model:value='formModel.mod'
|
||||
:options='[
|
||||
{ label: "周期执行", value: "period" },
|
||||
{ label: "执行一次", value: "once" },
|
||||
]'
|
||||
option-type='button'
|
||||
button-style='solid'
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-space v-if='showOnce' style='display: flex;gap: 24px'>
|
||||
<a-form-item :name="['once', 'time']">
|
||||
<a-time-picker valueFormat='HH:mm:ss' v-model:value='formModel.once.time' style='width: 100%' format='HH:mm:ss' />
|
||||
</a-form-item>
|
||||
<a-form-item> 执行一次 </a-form-item>
|
||||
</a-space>
|
||||
<a-space v-if='showPeriod' style='display: flex;gap: 24px'>
|
||||
<a-form-item>
|
||||
<a-time-range-picker
|
||||
valueFormat='HH:mm:ss'
|
||||
:value='[
|
||||
formModel.period.from,
|
||||
formModel.period.to,
|
||||
]'
|
||||
@change='(v) => {
|
||||
formModel.period.from = v[0]
|
||||
formModel.period.to = v[1]
|
||||
}'
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>每</a-form-item>
|
||||
<a-form-item
|
||||
:name='["period", "every"]'
|
||||
:rules='[{ required: true, message: "请输入时间" }]'
|
||||
>
|
||||
<a-input-number
|
||||
placeholder='请输入时间'
|
||||
style='max-width: 170px'
|
||||
:precision='0'
|
||||
:min='1'
|
||||
:max='59'
|
||||
v-model:value='formModel.period.every'
|
||||
>
|
||||
<template #addonAfter>
|
||||
<a-select
|
||||
v-model:value='formModel.period.unit'
|
||||
:options='[
|
||||
{ label: "秒", value: "seconds" },
|
||||
{ label: "分", value: "minutes" },
|
||||
{ label: "小时", value: "hours" },
|
||||
]'
|
||||
/>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item>执行一次</a-form-item>
|
||||
</a-space>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='Timer'>
|
||||
import type { PropType } from 'vue'
|
||||
import moment from 'moment'
|
||||
|
||||
type NameType = string[] | string
|
||||
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: [String, Array] as PropType<NameType>,
|
||||
default: ''
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const formModel = reactive({
|
||||
trigger: 'week',
|
||||
when: [],
|
||||
mod: 'period',
|
||||
cron: undefined,
|
||||
once: {
|
||||
time: ''
|
||||
},
|
||||
period: {
|
||||
from: moment(new Date()).startOf('day').format('HH:mm:ss'),
|
||||
to: moment(new Date()).endOf('day').format('HH:mm:ss'),
|
||||
every: 1,
|
||||
unit: 'seconds'
|
||||
}
|
||||
})
|
||||
|
||||
const showCron = computed(() => {
|
||||
return formModel.trigger === 'cron'
|
||||
})
|
||||
|
||||
const showOnce = computed(() => {
|
||||
return formModel.trigger !== 'cron' && formModel.mod === 'once'
|
||||
})
|
||||
|
||||
const showPeriod = computed(() => {
|
||||
return formModel.trigger !== 'cron' && formModel.mod === 'period'
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,167 @@
|
|||
<template>
|
||||
<div :class='classNames'>
|
||||
<div
|
||||
v-for='item in options'
|
||||
:key='item.value'
|
||||
:class='[
|
||||
"trigger-way-item",
|
||||
value === item.value ? "active" : "",
|
||||
labelBottom ? "label-bottom" : ""
|
||||
]'
|
||||
@click='() => click(item.value)'
|
||||
>
|
||||
<div class='way-item-title'>
|
||||
<span class='label'>{{ item.label }}</span>
|
||||
<a-popover v-if='item.tip' :content='item.tip'>
|
||||
<AIcon type='QuestionCircleOutlined' class='icon' />
|
||||
</a-popover>
|
||||
</div>
|
||||
<div class='way-item-image'>
|
||||
<img
|
||||
width='48'
|
||||
v-bind='item.imgProps'
|
||||
:src='item.img'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='TopCard'>
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
type optionsType = {
|
||||
label: string
|
||||
value: string
|
||||
img?: string
|
||||
tip?: string
|
||||
imgProps: Record<string, any>
|
||||
}
|
||||
|
||||
type Emit = {
|
||||
(e: 'update:value', data: string): void
|
||||
(e: 'select', data: string): void
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Array as PropType<optionsType[]>,
|
||||
default: () => ([])
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
class: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
labelBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const classNames = computed(() => {
|
||||
return [
|
||||
props.class,
|
||||
'trigger-way-warp',
|
||||
props.disabled ? 'disabled' : ''
|
||||
]
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const click = (value: string) => {
|
||||
emit('update:value', value)
|
||||
emit('select', value)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
.trigger-way-warp {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px 24px;
|
||||
width: 100%;
|
||||
|
||||
.trigger-way-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 237px;
|
||||
//width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #e0e4e8;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
.way-item-title {
|
||||
span {
|
||||
font-size: 16px;
|
||||
}
|
||||
.label {
|
||||
padding-right: 6px;
|
||||
color: rgba(#000, 0.64);
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: rgba(#000, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.way-item-image {
|
||||
margin: 0 !important;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
//color: @primary-color-hover;
|
||||
.way-item-image {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: @primary-color-active;
|
||||
.way-item-image {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.label-bottom {
|
||||
flex-direction: column-reverse;
|
||||
grid-gap: 16px;
|
||||
gap: 0;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
padding: 8px 16px;
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.trigger-way-item {
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
color: initial;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -9,6 +9,11 @@ type State = {
|
|||
text: string;
|
||||
};
|
||||
|
||||
export type optionItem = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
type Action = {
|
||||
executor: string;
|
||||
configuration: Record<string, unknown>;
|
||||
|
@ -311,3 +316,9 @@ export interface FormModelType {
|
|||
options?: Record<string, any>;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export type metadataType = {
|
||||
properties?: any[]
|
||||
functions?: any[]
|
||||
events?: any[]
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div class="form-label-container">
|
||||
<span class="text">{{ props.text }}</span>
|
||||
<span class="required">*</span>
|
||||
<span class="required" v-show="props.required">*</span>
|
||||
<a-tooltip>
|
||||
<template #title>{{ props.tooltip }}</template>
|
||||
<AIcon type="QuestionCircleOutlined" style="color: #00000073;cursor: inherit;" />
|
||||
<AIcon type="QuestionCircleOutlined" class="icon" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -24,11 +24,15 @@ const props = defineProps<{
|
|||
|
||||
.required {
|
||||
display: inline-block;
|
||||
margin-right: 4px;
|
||||
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,49 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model:visible="dialog.visible"
|
||||
title="集成菜单"
|
||||
width="600px"
|
||||
@ok="dialog.handleOk"
|
||||
class="edit-dialog-container"
|
||||
:confirmLoading="dialog.loading"
|
||||
cancelText="取消"
|
||||
okText="确定"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
|
||||
const emits = defineEmits(['confirm']);
|
||||
// 弹窗相关
|
||||
const dialog = reactive({
|
||||
visible: false,
|
||||
loading: false,
|
||||
|
||||
handleOk: () => {
|
||||
emits('confirm');
|
||||
},
|
||||
/**
|
||||
* 设置表单类型
|
||||
* @param type 弹窗类型
|
||||
* @param defaultForm 表单回显对象
|
||||
*/
|
||||
changeVisible: () => {
|
||||
dialog.visible = true;
|
||||
}
|
||||
});
|
||||
// 将打开弹窗的操作暴露给父组件
|
||||
defineExpose({
|
||||
openDialog: dialog.changeVisible,
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,144 @@
|
|||
<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"
|
||||
v-if="props.valueType === 'input'"
|
||||
/>
|
||||
<a-select
|
||||
v-else-if="props.valueType === 'select'"
|
||||
v-model:value="record.value"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in props.valueOptions"
|
||||
:value="item.value"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</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 = withDefaults(
|
||||
defineProps<{
|
||||
value: optionsType;
|
||||
valueType?: 'input' | 'select';
|
||||
valueOptions?: optionsType;
|
||||
}>(),
|
||||
{
|
||||
valueType: 'input',
|
||||
},
|
||||
);
|
||||
const columns = [
|
||||
{
|
||||
title: 'KEY',
|
||||
dataIndex: 'key',
|
||||
width: '40%'
|
||||
},
|
||||
{
|
||||
title: 'VALUE',
|
||||
dataIndex: 'value',
|
||||
width: '40%'
|
||||
},
|
||||
{
|
||||
title: ' ',
|
||||
dataIndex: 'action',
|
||||
width: '20%'
|
||||
},
|
||||
];
|
||||
|
||||
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>
|
|
@ -3,351 +3,9 @@
|
|||
<a-card class="save-container">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="14">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form.data"
|
||||
layout="vertical"
|
||||
class="form"
|
||||
>
|
||||
<a-form-item label="名称" name="name">
|
||||
<a-input
|
||||
v-model:value="form.data.name"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="应用" name="provider">
|
||||
<a-radio-group
|
||||
v-model:value="form.data.provider"
|
||||
class="radio-container"
|
||||
@change="form.data.integrationModes = []"
|
||||
>
|
||||
<a-radio-button value="internal-standalone">
|
||||
<div>
|
||||
<a-image
|
||||
:preview="false"
|
||||
:src="
|
||||
getImage('/apply/provider1.png')
|
||||
"
|
||||
width="64px"
|
||||
height="64px"
|
||||
/>
|
||||
<p>内部独立应用</p>
|
||||
</div>
|
||||
</a-radio-button>
|
||||
<a-radio-button value="internal-integrated">
|
||||
<div>
|
||||
<a-image
|
||||
:preview="false"
|
||||
:src="
|
||||
getImage('/apply/provider2.png')
|
||||
"
|
||||
/>
|
||||
<p>内部集成应用</p>
|
||||
</div>
|
||||
</a-radio-button>
|
||||
<a-radio-button value="wechat-webapp">
|
||||
<div>
|
||||
<a-image
|
||||
:preview="false"
|
||||
:src="
|
||||
getImage('/apply/provider3.png')
|
||||
"
|
||||
/>
|
||||
<p>微信网站应用</p>
|
||||
</div>
|
||||
</a-radio-button>
|
||||
<a-radio-button value="dingtalk-ent-app">
|
||||
<div>
|
||||
<a-image
|
||||
:preview="false"
|
||||
:src="
|
||||
getImage('/apply/provider4.png')
|
||||
"
|
||||
/>
|
||||
<p>钉钉企业内部应用</p>
|
||||
</div>
|
||||
</a-radio-button>
|
||||
<a-radio-button value="third-party">
|
||||
<div>
|
||||
<a-image
|
||||
:preview="false"
|
||||
:src="
|
||||
getImage('/apply/provider5.png')
|
||||
"
|
||||
/>
|
||||
<p>第三方应用</p>
|
||||
</div>
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="接入方式" name="integrationModes">
|
||||
<a-checkbox-group
|
||||
v-model:value="form.data.integrationModes"
|
||||
:options="joinOptions"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-collapse
|
||||
v-model:activeKey="form.integrationModesISO"
|
||||
>
|
||||
<a-collapse-panel
|
||||
key="page"
|
||||
v-show="
|
||||
form.data.integrationModes.includes('page')
|
||||
"
|
||||
header="页面集成"
|
||||
>
|
||||
<a-form-item
|
||||
:name="['page', 'baseUrl']"
|
||||
class="resetLabel"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<FormLabel
|
||||
text="接入地址"
|
||||
required
|
||||
tooltip="填写访问其它平台的地址"
|
||||
/>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="form.data.page.baseUrl"
|
||||
placeholder="请输入接入地址"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="路由方式"
|
||||
:name="['page', 'routeType']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="form.data.page.routeType"
|
||||
style="width: 120px"
|
||||
>
|
||||
<a-select-option value="hash"
|
||||
>hash</a-select-option
|
||||
>
|
||||
<a-select-option value="history"
|
||||
>history</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel
|
||||
key="apiClient"
|
||||
v-show="
|
||||
form.data.integrationModes.includes(
|
||||
'apiClient',
|
||||
)
|
||||
"
|
||||
header="API客户端"
|
||||
>
|
||||
<a-form-item
|
||||
class="resetLabel"
|
||||
:name="['apiClient', 'baseUrl']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<FormLabel
|
||||
text="接口地址"
|
||||
required
|
||||
tooltip="访问Api服务的地址"
|
||||
/>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="
|
||||
form.data.apiClient.baseUrl
|
||||
"
|
||||
placeholder="请输入接入地址"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
class="resetLabel"
|
||||
:name="[
|
||||
'apiClient',
|
||||
'authConfig',
|
||||
'oauth2',
|
||||
'authorizationUrl',
|
||||
]"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<FormLabel
|
||||
text="授权地址"
|
||||
required
|
||||
tooltip="认证授权地址"
|
||||
/>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="
|
||||
form.data.apiClient.authConfig
|
||||
.oauth2.authorizationUrl
|
||||
"
|
||||
placeholder="请输入授权地址"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
class="resetLabel"
|
||||
:name="[
|
||||
'apiClient',
|
||||
'authConfig',
|
||||
'oauth2',
|
||||
'tokenUrl',
|
||||
]"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<FormLabel
|
||||
text="token地址"
|
||||
required
|
||||
tooltip="设置token令牌的地址"
|
||||
/>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="
|
||||
form.data.apiClient.authConfig
|
||||
.oauth2.tokenUrl
|
||||
"
|
||||
placeholder="请输入token地址"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="回调地址"
|
||||
:name="[
|
||||
'apiClient',
|
||||
'authConfig',
|
||||
'oauth2',
|
||||
'redirectUri',
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<FormLabel
|
||||
text="回调地址"
|
||||
tooltip="授权完成后跳转到具体页面的回调地址"
|
||||
/>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="
|
||||
form.data.apiClient.authConfig
|
||||
.oauth2.redirectUri
|
||||
"
|
||||
placeholder="请输入回调地址"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
class="resetLabel"
|
||||
:name="[
|
||||
'apiClient',
|
||||
'authConfig',
|
||||
'oauth2',
|
||||
'clientId',
|
||||
]"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<FormLabel
|
||||
text="appId"
|
||||
required
|
||||
tooltip="第三方应用唯一标识"
|
||||
/>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="
|
||||
form.data.apiClient.authConfig
|
||||
.oauth2.clientId
|
||||
"
|
||||
placeholder="请输入appId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
class="resetLabel"
|
||||
:name="[
|
||||
'apiClient',
|
||||
'authConfig',
|
||||
'oauth2',
|
||||
'clientSecret',
|
||||
]"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<FormLabel
|
||||
text="appKey"
|
||||
required
|
||||
tooltip="第三方应用唯一标识的密钥"
|
||||
/>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="
|
||||
form.data.apiClient.authConfig
|
||||
.oauth2.clientSecret
|
||||
"
|
||||
placeholder="请输入appKey"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="请求头"> </a-form-item>
|
||||
<a-form-item label="参数"> </a-form-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel
|
||||
key="apiServer"
|
||||
v-show="
|
||||
form.data.integrationModes.includes(
|
||||
'apiServer',
|
||||
)
|
||||
"
|
||||
header="API服务"
|
||||
>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel
|
||||
key="ssoClient"
|
||||
v-show="
|
||||
form.data.integrationModes.includes(
|
||||
'ssoClient',
|
||||
)
|
||||
"
|
||||
header="单点登录"
|
||||
>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<a-form-item label="说明" name="description">
|
||||
<a-textarea
|
||||
v-model:value="form.data.description"
|
||||
placeholder="请输入说明"
|
||||
showCount
|
||||
:maxlength="200"
|
||||
:rows="5"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-button v-if="!routeQuery.view">保存</a-button>
|
||||
<EditForm @change-apply-type="chengeType" />
|
||||
</a-col>
|
||||
<a-col :span="10"><Does :type="form.data.provider" /></a-col>
|
||||
<a-col :span="10"><Does :type="rightType" /></a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</page-container>
|
||||
|
@ -355,151 +13,11 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import Does from './components/Does.vue';
|
||||
import FormLabel from './components/FormLabel.vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import type { applyType, formType } from './typing';
|
||||
const routeQuery = useRoute().query;
|
||||
import EditForm from './components/EditForm.vue';
|
||||
import type { applyType } from './typing';
|
||||
|
||||
const initForm: formType = {
|
||||
name: '',
|
||||
provider: 'internal-standalone',
|
||||
integrationModes: [],
|
||||
config: '',
|
||||
description: '',
|
||||
page: {
|
||||
baseUrl: '',
|
||||
routeType: 'hash',
|
||||
},
|
||||
apiClient: {
|
||||
baseUrl: '',
|
||||
authConfig: {
|
||||
type: '',
|
||||
oauth2: {
|
||||
authorizationUrl: '',
|
||||
tokenUrl: '',
|
||||
redirectUri: '',
|
||||
clientId: '',
|
||||
clientSecret: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
const rightType = ref<applyType>('internal-standalone');
|
||||
const chengeType = (newType: applyType) => {
|
||||
rightType.value = newType;
|
||||
};
|
||||
const form = reactive({
|
||||
data: { ...initForm },
|
||||
integrationModesISO: [] as string[],
|
||||
});
|
||||
const joinOptions = computed(() => {
|
||||
if (form.data.provider === 'internal-standalone')
|
||||
return [
|
||||
{
|
||||
label: '页面集成',
|
||||
value: 'page',
|
||||
},
|
||||
{
|
||||
label: 'API客户端',
|
||||
value: 'apiClient',
|
||||
},
|
||||
{
|
||||
label: 'API服务',
|
||||
value: 'apiServer',
|
||||
},
|
||||
{
|
||||
label: '单点登录',
|
||||
value: 'ssoClient',
|
||||
},
|
||||
];
|
||||
else if (form.data.provider === 'internal-integrated')
|
||||
return [
|
||||
{
|
||||
label: '页面集成',
|
||||
value: 'page',
|
||||
},
|
||||
{
|
||||
label: 'API客户端',
|
||||
value: 'apiClient',
|
||||
},
|
||||
];
|
||||
else if (form.data.provider === 'wechat-webapp')
|
||||
return [
|
||||
{
|
||||
label: '单点登录',
|
||||
value: 'ssoClient',
|
||||
},
|
||||
];
|
||||
else if (form.data.provider === 'dingtalk-ent-app')
|
||||
return [
|
||||
{
|
||||
label: '单点登录',
|
||||
value: 'ssoClient',
|
||||
},
|
||||
];
|
||||
else if (form.data.provider === 'third-party')
|
||||
return [
|
||||
{
|
||||
label: '页面集成',
|
||||
value: 'page',
|
||||
},
|
||||
{
|
||||
label: 'API客户端',
|
||||
value: 'apiClient',
|
||||
},
|
||||
{
|
||||
label: 'API服务',
|
||||
value: 'apiServer',
|
||||
},
|
||||
{
|
||||
label: '单点登录',
|
||||
value: 'ssoClient',
|
||||
},
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.save-container {
|
||||
.form {
|
||||
.ant-form-item {
|
||||
&.resetLabel {
|
||||
:deep(.ant-form-item-required) {
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
.radio-container {
|
||||
.ant-radio-button-wrapper {
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
padding: 0 15px;
|
||||
box-sizing: content-box;
|
||||
margin-right: 20px;
|
||||
|
||||
> :last-child {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
> div {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
:deep(.ant-image) {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,28 +3,93 @@ export type applyType = 'internal-standalone'
|
|||
| '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 = {
|
||||
id?:string,
|
||||
name: string;
|
||||
provider: applyType;
|
||||
integrationModes: string[];
|
||||
config: string;
|
||||
description: string;
|
||||
page: {
|
||||
baseUrl:string,
|
||||
routeType:'hash' | 'history'
|
||||
},
|
||||
apiClient: {
|
||||
page: { // 页面集成
|
||||
baseUrl: string,
|
||||
authConfig: {
|
||||
type:string,
|
||||
oauth2 :{
|
||||
authorizationUrl:string,
|
||||
tokenUrl: string,
|
||||
redirectUri:string,
|
||||
clientId:string,
|
||||
clientSecret: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 }, // 基本信息
|
||||
token: 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单点登录配置
|
||||
type?: string, // 认证方式
|
||||
authorizationUrl: string, // 授权地址
|
||||
redirectUri: string, // 重定向地址
|
||||
clientId: string, // 客户端ID
|
||||
clientSecret: string, // 客户端密钥
|
||||
userInfoUrl: string, // 用户信息接口
|
||||
scope: string, // scope
|
||||
logoUrl?:string, // logo
|
||||
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, // 默认密码
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue