Merge branch 'dev' into dev-hub
This commit is contained in:
commit
a77997d87f
|
@ -37,6 +37,7 @@
|
||||||
"unplugin-vue-components": "^0.22.12",
|
"unplugin-vue-components": "^0.22.12",
|
||||||
"vite-plugin-monaco-editor": "^1.1.0",
|
"vite-plugin-monaco-editor": "^1.1.0",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
|
"vue-json-viewer": "^3.0.4",
|
||||||
"vue-router": "^4.1.6",
|
"vue-router": "^4.1.6",
|
||||||
"vue3-markdown-it": "^1.0.10",
|
"vue3-markdown-it": "^1.0.10",
|
||||||
"vue3-ts-jsoneditor": "^2.7.1"
|
"vue3-ts-jsoneditor": "^2.7.1"
|
||||||
|
|
|
@ -466,4 +466,20 @@ export const saveEdgeMap = (deviceId: string, data?: any) => server.post(`/edge/
|
||||||
* @param params
|
* @param params
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getPropertyData = (deviceId: string, params: Record<string, unknown>) => server.get(`/device-instance/${deviceId}/properties/_query`, params)
|
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),
|
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),
|
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`),
|
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`),
|
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),
|
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}`);
|
|
@ -5,4 +5,6 @@ export const modify = (id: string, data: any) => server.put(`/scene/${id}`, data
|
||||||
|
|
||||||
export const save = (data: any) => server.post(`/scene`, data)
|
export const save = (data: any) => server.post(`/scene`, data)
|
||||||
|
|
||||||
export const detail = (id: string) => server.get(`/scene/${id}`)
|
export const detail = (id: string) => server.get(`/scene/${id}`)
|
||||||
|
|
||||||
|
export const query = (data: any) => server.post('/scene/_query/',data);
|
|
@ -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 changeApplyStatus_api = (id:string,data: any) => server.put(`/application/${id}`, data)
|
||||||
// 删除应用
|
// 删除应用
|
||||||
export const delApply_api = (id:string) => server.remove(`/application/${id}`)
|
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: {
|
class: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
}
|
},
|
||||||
|
// defaultTerms: {
|
||||||
|
// type: Object,
|
||||||
|
// default: () => ({})
|
||||||
|
// }
|
||||||
})
|
})
|
||||||
|
|
||||||
const searchRef = ref(null)
|
const searchRef = ref(null)
|
||||||
|
@ -223,6 +227,7 @@ const handleParamsFormat = () => {
|
||||||
*/
|
*/
|
||||||
const searchSubmit = () => {
|
const searchSubmit = () => {
|
||||||
emit('search', handleParamsFormat())
|
emit('search', handleParamsFormat())
|
||||||
|
console.log('searchSubmit')
|
||||||
if (props.type === 'advanced') {
|
if (props.type === 'advanced') {
|
||||||
addUrlParams()
|
addUrlParams()
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,6 +144,10 @@ const JTable = defineComponent<JTableProps>({
|
||||||
pageSize: 12
|
pageSize: 12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
scroll: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { x: 1366 }
|
||||||
}
|
}
|
||||||
} as any,
|
} as any,
|
||||||
setup(props: JTableProps, { slots, emit, expose }) {
|
setup(props: JTableProps, { slots, emit, expose }) {
|
||||||
|
@ -260,7 +264,7 @@ const JTable = defineComponent<JTableProps>({
|
||||||
/**
|
/**
|
||||||
* 导出方法
|
* 导出方法
|
||||||
*/
|
*/
|
||||||
expose({ reload })
|
expose({ reload, _dataSource })
|
||||||
|
|
||||||
return () => <Spin spinning={loading.value}>
|
return () => <Spin spinning={loading.value}>
|
||||||
<div class={styles["jtable-body"]} style={{ ...props.bodyStyle }}>
|
<div class={styles["jtable-body"]} style={{ ...props.bodyStyle }}>
|
||||||
|
@ -331,7 +335,7 @@ const JTable = defineComponent<JTableProps>({
|
||||||
pagination={false}
|
pagination={false}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
rowSelection={props.rowSelection}
|
rowSelection={props.rowSelection}
|
||||||
scroll={{ x: 1366 }}
|
scroll={props.scroll}
|
||||||
v-slots={{
|
v-slots={{
|
||||||
bodyCell: (dt: Record<string, any>) => {
|
bodyCell: (dt: Record<string, any>) => {
|
||||||
const { column, record } = dt;
|
const { column, record } = dt;
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { filterAsnycRouter, MenuItem } from '@/utils/menu'
|
||||||
import { isArray } from 'lodash-es'
|
import { isArray } from 'lodash-es'
|
||||||
import { usePermissionStore } from './permission'
|
import { usePermissionStore } from './permission'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import { onlyMessage } from '@/utils/comm'
|
||||||
|
|
||||||
const defaultOwnParams = [
|
const defaultOwnParams = [
|
||||||
{
|
{
|
||||||
|
@ -77,6 +79,7 @@ export const useMenuStore = defineStore({
|
||||||
name, params, query
|
name, params, query
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
onlyMessage('暂无权限,请联系管理员', 'error')
|
||||||
console.warn(`没有找到对应的页面: ${name}`)
|
console.warn(`没有找到对应的页面: ${name}`)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -68,7 +68,7 @@ const defaultOptions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSceneStore = defineStore('scene', () => {
|
export const useSceneStore = defineStore('scene', () => {
|
||||||
const data = reactive<FormModelType | any>({
|
const data = reactive<FormModelType>({
|
||||||
trigger: { type: ''},
|
trigger: { type: ''},
|
||||||
options: defaultOptions,
|
options: defaultOptions,
|
||||||
branches: defaultBranches,
|
branches: defaultBranches,
|
||||||
|
@ -116,67 +116,3 @@ export const useSceneStore = defineStore('scene', () => {
|
||||||
getDetail
|
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: {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// })
|
|
|
@ -7,4 +7,12 @@ export const isUrl = (path: string): boolean => urlReg.test(path)
|
||||||
|
|
||||||
export const inputReg = /^[a-zA-Z0-9_\-]+$/
|
export const inputReg = /^[a-zA-Z0-9_\-]+$/
|
||||||
|
|
||||||
export const isInput = (value: string) => inputReg.test(value)
|
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 AIcon from "@/components/AIcon";
|
||||||
|
import { useInstanceStore } from "@/store/instance";
|
||||||
|
import { useMenuStore } from "@/store/menu";
|
||||||
import { Button, Descriptions, Modal } from "ant-design-vue"
|
import { Button, Descriptions, Modal } from "ant-design-vue"
|
||||||
import styles from './index.module.less'
|
import styles from './index.module.less'
|
||||||
|
|
||||||
|
@ -14,6 +16,10 @@ const ManualInspection = defineComponent({
|
||||||
|
|
||||||
const { data } = props
|
const { data } = props
|
||||||
|
|
||||||
|
const instanceStore = useInstanceStore();
|
||||||
|
|
||||||
|
const menuStory = useMenuStore();
|
||||||
|
|
||||||
const dataRender = () => {
|
const dataRender = () => {
|
||||||
if (data.type === 'device' || data.type === 'product') {
|
if (data.type === 'device' || data.type === 'product') {
|
||||||
return (
|
return (
|
||||||
|
@ -207,7 +213,13 @@ const ManualInspection = defineComponent({
|
||||||
emit('save', data)
|
emit('save', data)
|
||||||
}}
|
}}
|
||||||
onCancel={() => {
|
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>
|
<div style={{ display: 'flex' }}>{dataRender()}</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -11,6 +11,8 @@ import _ from "lodash"
|
||||||
import DiagnosticAdvice from './DiagnosticAdvice'
|
import DiagnosticAdvice from './DiagnosticAdvice'
|
||||||
import ManualInspection from './ManualInspection'
|
import ManualInspection from './ManualInspection'
|
||||||
import { deployDevice } from "@/api/initHome"
|
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'
|
type TypeProps = 'network' | 'child-device' | 'media' | 'cloud' | 'channel'
|
||||||
|
|
||||||
|
@ -41,6 +43,7 @@ const Status = defineComponent({
|
||||||
const diagnoseData = ref<Partial<Record<string, any>>>()
|
const diagnoseData = ref<Partial<Record<string, any>>>()
|
||||||
|
|
||||||
const bindParentVisible = ref<boolean>(false)
|
const bindParentVisible = ref<boolean>(false)
|
||||||
|
const menuStory = useMenuStore();
|
||||||
|
|
||||||
const configuration = reactive<{
|
const configuration = reactive<{
|
||||||
product: Record<string, any>,
|
product: Record<string, any>,
|
||||||
|
@ -57,19 +60,8 @@ const Status = defineComponent({
|
||||||
artificialData.value = params
|
artificialData.value = params
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
const jumpAccessConfig = () => {
|
const jumpAccessConfig = () => {
|
||||||
// const purl = getMenuPathByCode(MENUS_CODE['device/Product/Detail']);
|
menuStory.jumpPage('device/Product/Detail', { id: unref(device).productId, tab: 'access' });
|
||||||
// if (purl) {
|
|
||||||
// history.push(
|
|
||||||
// `${getMenuPathByParams(MENUS_CODE['device/Product/Detail'], device.productId)}`,
|
|
||||||
// {
|
|
||||||
// tab: 'access',
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// } else {
|
|
||||||
// message.error('规则可能有加密处理,请联系管理员');
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const jumpDeviceConfig = () => {
|
const jumpDeviceConfig = () => {
|
||||||
|
@ -123,34 +115,40 @@ const Status = defineComponent({
|
||||||
<Badge
|
<Badge
|
||||||
status="default"
|
status="default"
|
||||||
text={
|
text={
|
||||||
<span>网络组件已禁用,请先<Popconfirm
|
<span>网络组件已禁用,请先
|
||||||
title="确认启用"
|
<PermissionButton
|
||||||
onConfirm={async () => {
|
type="link"
|
||||||
const res = await startNetwork(
|
hasPermission="link/Type:action"
|
||||||
unref(gateway)?.channelId,
|
popConfirm={{
|
||||||
);
|
title: '确认启用',
|
||||||
if (res.status === 200) {
|
onConfirm: async () => {
|
||||||
message.success('操作成功!');
|
const res = await startNetwork(
|
||||||
list.value = modifyArrayList(
|
unref(gateway)?.channelId,
|
||||||
list.value,
|
);
|
||||||
{
|
if (res.status === 200) {
|
||||||
key: 'network',
|
message.success('操作成功!');
|
||||||
name: '网络组件',
|
list.value = modifyArrayList(
|
||||||
desc: '诊断网络组件配置是否正确,配置错误将导致设备连接失败',
|
list.value,
|
||||||
status: 'success',
|
{
|
||||||
text: '正常',
|
key: 'network',
|
||||||
info: null,
|
name: '网络组件',
|
||||||
},
|
desc: '诊断网络组件配置是否正确,配置错误将导致设备连接失败',
|
||||||
);
|
status: 'success',
|
||||||
}
|
text: '正常',
|
||||||
}}
|
info: null,
|
||||||
>
|
},
|
||||||
<Button type="link" style="padding: 0">启用</Button>
|
);
|
||||||
</Popconfirm></span>
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
启用
|
||||||
|
</PermissionButton>
|
||||||
|
</span>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div >
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<div class={styles.infoItem}>
|
<div class={styles.infoItem}>
|
||||||
|
@ -287,28 +285,31 @@ const Status = defineComponent({
|
||||||
<Badge
|
<Badge
|
||||||
status="default"
|
status="default"
|
||||||
text={<span>设备接入网关已禁用,请先
|
text={<span>设备接入网关已禁用,请先
|
||||||
<Popconfirm
|
<PermissionButton
|
||||||
title="确认启用"
|
hasPermission="link/Type:action"
|
||||||
onConfirm={async () => {
|
popConfirm={{
|
||||||
const resp = await startGateway(unref(device).accessId || '');
|
title: '确认启用',
|
||||||
if (resp.status === 200) {
|
onConfirm: async () => {
|
||||||
message.success('操作成功!');
|
const resp = await startGateway(unref(device).accessId || '');
|
||||||
list.value = modifyArrayList(
|
if (resp.status === 200) {
|
||||||
list.value,
|
message.success('操作成功!');
|
||||||
{
|
list.value = modifyArrayList(
|
||||||
key: 'gateway',
|
list.value,
|
||||||
name: '设备接入网关',
|
{
|
||||||
desc: desc,
|
key: 'gateway',
|
||||||
status: 'success',
|
name: '设备接入网关',
|
||||||
text: '正常',
|
desc: desc,
|
||||||
info: null,
|
status: 'success',
|
||||||
},
|
text: '正常',
|
||||||
);
|
info: null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button type="link" style="padding: 0">启用</Button>
|
启用
|
||||||
</Popconfirm>
|
</PermissionButton>
|
||||||
</span>}
|
</span>}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -411,28 +412,32 @@ const Status = defineComponent({
|
||||||
status="default"
|
status="default"
|
||||||
text={
|
text={
|
||||||
<span>
|
<span>
|
||||||
设备接入网关已禁用,请先<Popconfirm
|
设备接入网关已禁用,请先
|
||||||
title="确认启用"
|
<PermissionButton
|
||||||
onConfirm={async () => {
|
hasPermission="link/AccessConfig:action"
|
||||||
const resp = await startGateway(unref(device).accessId || '');
|
popConfirm={{
|
||||||
if (resp.status === 200) {
|
title: '确认启用',
|
||||||
message.success('操作成功!');
|
onConfirm: async () => {
|
||||||
list.value = modifyArrayList(
|
const resp = await startGateway(unref(device).accessId || '');
|
||||||
list.value,
|
if (resp.status === 200) {
|
||||||
{
|
message.success('操作成功!');
|
||||||
key: 'gateway',
|
list.value = modifyArrayList(
|
||||||
name: '设备接入网关',
|
list.value,
|
||||||
desc: desc,
|
{
|
||||||
status: 'success',
|
key: 'gateway',
|
||||||
text: '正常',
|
name: '设备接入网关',
|
||||||
info: null,
|
desc: desc,
|
||||||
},
|
status: 'success',
|
||||||
);
|
text: '正常',
|
||||||
|
info: null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button type="link" style="padding: 0">启用</Button>
|
启用
|
||||||
</Popconfirm>
|
</PermissionButton>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -519,28 +524,32 @@ const Status = defineComponent({
|
||||||
status="default"
|
status="default"
|
||||||
text={
|
text={
|
||||||
<span>
|
<span>
|
||||||
网关父设备已禁用,请先<Popconfirm
|
网关父设备已禁用,请先
|
||||||
title="确认启用"
|
<PermissionButton
|
||||||
onConfirm={async () => {
|
hasPermission="device/Product:action"
|
||||||
const resp = await _deploy(response?.result?.id || '');
|
popConfirm={{
|
||||||
if (resp.status === 200) {
|
title: '确认启用',
|
||||||
message.success('操作成功!');
|
onConfirm: async () => {
|
||||||
list.value = modifyArrayList(
|
const resp = await _deploy(response?.result?.id || '');
|
||||||
list.value,
|
if (resp.status === 200) {
|
||||||
{
|
message.success('操作成功!');
|
||||||
key: 'parent-device',
|
list.value = modifyArrayList(
|
||||||
name: '网关父设备',
|
list.value,
|
||||||
desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
|
{
|
||||||
status: 'success',
|
key: 'parent-device',
|
||||||
text: '正常',
|
name: '网关父设备',
|
||||||
info: null,
|
desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
|
||||||
},
|
status: 'success',
|
||||||
);
|
text: '正常',
|
||||||
|
info: null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button type="link" style="padding: 0">启用</Button>
|
启用
|
||||||
</Popconfirm>
|
</PermissionButton>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -623,28 +632,32 @@ const Status = defineComponent({
|
||||||
status="default"
|
status="default"
|
||||||
text={
|
text={
|
||||||
<span>
|
<span>
|
||||||
产品已禁用,请<Popconfirm
|
产品已禁用,请
|
||||||
title="确认启用"
|
<PermissionButton
|
||||||
onConfirm={async () => {
|
hasPermission="device/Product:action"
|
||||||
const resp = await _deployProduct(unref(device).productId || '');
|
popConfirm={{
|
||||||
if (resp.status === 200) {
|
title: '确认启用',
|
||||||
message.success('操作成功!');
|
onConfirm: async () => {
|
||||||
list.value = modifyArrayList(
|
const resp = await _deployProduct(unref(device).productId || '');
|
||||||
list.value,
|
if (resp.status === 200) {
|
||||||
{
|
message.success('操作成功!');
|
||||||
key: 'product',
|
list.value = modifyArrayList(
|
||||||
name: '产品状态',
|
list.value,
|
||||||
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
{
|
||||||
status: 'success',
|
key: 'product',
|
||||||
text: '正常',
|
name: '产品状态',
|
||||||
info: null,
|
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
||||||
},
|
status: 'success',
|
||||||
);
|
text: '正常',
|
||||||
|
info: null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button type="link" style="padding: 0">启用</Button>
|
启用
|
||||||
</Popconfirm>
|
</PermissionButton>
|
||||||
产品
|
产品
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
@ -695,29 +708,34 @@ const Status = defineComponent({
|
||||||
status="default"
|
status="default"
|
||||||
text={
|
text={
|
||||||
<span>
|
<span>
|
||||||
设备已禁用,请<Popconfirm
|
设备已禁用,请
|
||||||
title="确认启用"
|
<PermissionButton
|
||||||
onConfirm={async () => {
|
hasPermission="device/Instance:action"
|
||||||
const resp = await _deploy(unref(device)?.id || '');
|
popConfirm={{
|
||||||
if (resp.status === 200) {
|
title: '确认启用',
|
||||||
instanceStore.current.state = { value: 'offline', text: '离线' }
|
onConfirm: async () => {
|
||||||
message.success('操作成功!');
|
const resp = await _deploy(unref(device)?.id || '');
|
||||||
list.value = modifyArrayList(
|
if (resp.status === 200) {
|
||||||
list.value,
|
instanceStore.current.state = { value: 'offline', text: '离线' }
|
||||||
{
|
message.success('操作成功!');
|
||||||
key: 'device',
|
list.value = modifyArrayList(
|
||||||
name: '设备状态',
|
list.value,
|
||||||
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
{
|
||||||
status: 'success',
|
key: 'device',
|
||||||
text: '正常',
|
name: '设备状态',
|
||||||
info: null,
|
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
||||||
},
|
status: 'success',
|
||||||
);
|
text: '正常',
|
||||||
|
info: null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button type="link" style="padding: 0">启用</Button>
|
启用
|
||||||
</Popconfirm>设备
|
</PermissionButton>
|
||||||
|
设备
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:request="_getEventList"
|
:request="_getEventList"
|
||||||
model="TABLE"
|
model="TABLE"
|
||||||
:bodyStyle="{padding: '0 24px'}"
|
:bodyStyle="{ padding: '0 24px' }"
|
||||||
>
|
>
|
||||||
<template #timestamp="slotProps">
|
<template #timestamp="slotProps">
|
||||||
{{ moment(slotProps.timestamp).format('YYYY-MM-DD HH:mm:ss') }}
|
{{ moment(slotProps.timestamp).format('YYYY-MM-DD HH:mm:ss') }}
|
||||||
|
@ -19,18 +19,18 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import moment from 'moment'
|
import moment from 'moment';
|
||||||
import { getEventList } from '@/api/device/instance'
|
import { getEventList } from '@/api/device/instance';
|
||||||
import { useInstanceStore } from '@/store/instance'
|
import { useInstanceStore } from '@/store/instance';
|
||||||
import { Modal } from 'ant-design-vue'
|
import { Modal } from 'ant-design-vue';
|
||||||
|
|
||||||
const events = defineProps({
|
const events = defineProps({
|
||||||
data: {
|
data: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {}
|
default: () => {},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
const instanceStore = useInstanceStore()
|
const instanceStore = useInstanceStore();
|
||||||
|
|
||||||
const columns = ref<Record<string, any>>([
|
const columns = ref<Record<string, any>>([
|
||||||
{
|
{
|
||||||
|
@ -38,43 +38,52 @@ const columns = ref<Record<string, any>>([
|
||||||
dataIndex: 'timestamp',
|
dataIndex: 'timestamp',
|
||||||
key: 'timestamp',
|
key: 'timestamp',
|
||||||
scopedSlots: true,
|
scopedSlots: true,
|
||||||
|
search: {
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
dataIndex: 'action',
|
dataIndex: 'action',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
scopedSlots: true,
|
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(() => {
|
watchEffect(() => {
|
||||||
if(events.data?.valueType?.type === 'object'){
|
if (events.data?.valueType?.type === 'object') {
|
||||||
(events.data.valueType?.properties || []).map((i: any) => {
|
(events.data.valueType?.properties || []).map((i: any) => {
|
||||||
columns.value.splice(0, 0, {
|
columns.value.splice(0, 0, {
|
||||||
key: i.id,
|
key: i.id,
|
||||||
title: i.name,
|
title: i.name,
|
||||||
dataIndex: `${i.id}_format`
|
dataIndex: `${i.id}_format`,
|
||||||
})
|
search: {
|
||||||
})
|
type: 'string',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
columns.value.splice(0, 0, {
|
columns.value.splice(0, 0, {
|
||||||
title: '数据',
|
title: '数据',
|
||||||
dataIndex: 'value',
|
dataIndex: 'value',
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
const detail = () => {
|
const detail = () => {
|
||||||
Modal.info({
|
Modal.info({
|
||||||
title: () => '详情',
|
title: () => '详情',
|
||||||
width: 850,
|
width: 850,
|
||||||
content: () => h('div', {}, [
|
content: () => h('div', {}, [h('p', '暂未开发')]),
|
||||||
h('p', '暂未开发'),
|
okText: '关闭',
|
||||||
]),
|
});
|
||||||
okText: '关闭'
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
|
@ -1,3 +1,54 @@
|
||||||
|
<!-- 坐标点拾取组件 -->
|
||||||
<template>
|
<template>
|
||||||
<div>AMap</div>
|
<div style="width: 100%; height: 400px">
|
||||||
</template>
|
<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>
|
<template>
|
||||||
<div>Charts</div>
|
<a-spin :spinning="loading">
|
||||||
</template>
|
<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'">
|
<template v-if="column.key === 'timestamp'">
|
||||||
{{ moment(record.timestamp).format('YYYY-MM-DD HH:mm:ss') }}
|
{{ moment(record.timestamp).format('YYYY-MM-DD HH:mm:ss') }}
|
||||||
</template>
|
</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'">
|
<template v-else-if="column.key === 'action'">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button
|
<a-button
|
||||||
|
@ -30,7 +37,7 @@
|
||||||
@click="_download(record)"
|
@click="_download(record)"
|
||||||
><AIcon type="DownloadOutlined"
|
><AIcon type="DownloadOutlined"
|
||||||
/></a-button>
|
/></a-button>
|
||||||
<a-button type="link"
|
<a-button type="link" @click="showDetail(record)"
|
||||||
><AIcon type="SearchOutlined"
|
><AIcon type="SearchOutlined"
|
||||||
/></a-button>
|
/></a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
|
@ -38,6 +45,28 @@
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</div>
|
</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>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -46,6 +75,8 @@ import { useInstanceStore } from '@/store/instance';
|
||||||
import encodeQuery from '@/utils/encodeQuery';
|
import encodeQuery from '@/utils/encodeQuery';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { getType } from '../index';
|
import { getType } from '../index';
|
||||||
|
import ValueRender from '../ValueRender.vue';
|
||||||
|
import JsonViewer from 'vue-json-viewer';
|
||||||
|
|
||||||
const _props = defineProps({
|
const _props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
|
@ -57,8 +88,11 @@ const _props = defineProps({
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const instanceStore = useInstanceStore();
|
const instanceStore = useInstanceStore();
|
||||||
const dataSource = ref({});
|
const dataSource = ref({});
|
||||||
|
const current = ref<any>({});
|
||||||
|
const visible = ref<boolean>(false);
|
||||||
|
|
||||||
const columns = computed(() => {
|
const columns = computed(() => {
|
||||||
const arr: any[] = [
|
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 queryPropertyData = async (params: any) => {
|
||||||
const resp = await getPropertyData(
|
const resp = await getPropertyData(
|
||||||
instanceStore.current.id,
|
instanceStore.current.id,
|
||||||
|
@ -99,7 +138,7 @@ const queryPropertyData = async (params: any) => {
|
||||||
...params,
|
...params,
|
||||||
terms: {
|
terms: {
|
||||||
property: _props.data.id,
|
property: _props.data.id,
|
||||||
timestamp$BTW: _props.time?.length ? _props.time : [],
|
timestamp$BTW: _props.time,
|
||||||
},
|
},
|
||||||
sorts: { timestamp: 'desc' },
|
sorts: { timestamp: 'desc' },
|
||||||
}),
|
}),
|
||||||
|
@ -109,14 +148,20 @@ const queryPropertyData = async (params: any) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
watchEffect(() => {
|
watch(
|
||||||
if (_props.data.id) {
|
() => [_props.data.id, _props.time],
|
||||||
queryPropertyData({
|
([newVal]) => {
|
||||||
pageSize: _props.data.valueType?.type === 'file' ? 5 : 10,
|
if (newVal) {
|
||||||
pageIndex: 0,
|
queryPropertyData({
|
||||||
});
|
pageSize: _props.data.valueType?.type === 'file' ? 5 : 10,
|
||||||
|
pageIndex: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true, immediate: true
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
const onChange = (page: any) => {
|
const onChange = (page: any) => {
|
||||||
queryPropertyData({
|
queryPropertyData({
|
||||||
|
@ -140,4 +185,10 @@ const _download = (record: any) => {
|
||||||
//移除
|
//移除
|
||||||
document.body.removeChild(downNode);
|
document.body.removeChild(downNode);
|
||||||
};
|
};
|
||||||
</script>
|
</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">
|
<a-modal title="详情" visible width="50vw" @ok="onCancel" @cancel="onCancel">
|
||||||
<div style="margin-bottom: 10px"><TimeComponent v-model="dateValue" /></div>
|
<div style="margin-bottom: 10px"><TimeComponent v-model="dateValue" /></div>
|
||||||
<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="列表">
|
<a-tab-pane key="table" tab="列表">
|
||||||
<Table :data="props.data" :time="_getTimes" />
|
<Table :data="props.data" :time="_getTimes" />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="charts" tab="图表">
|
<a-tab-pane key="charts" tab="图表">
|
||||||
<Charts />
|
<Charts :data="props.data" :time="_getTimes" />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="geo" tab="轨迹">
|
<a-tab-pane key="geo" tab="轨迹" v-if="data?.valueType?.type === 'geoPoint'">
|
||||||
<AMap />
|
<PropertyAMap :data="props.data" :time="_getTimes" />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
import type { Dayjs } from 'dayjs';
|
import type { Dayjs } from 'dayjs';
|
||||||
import TimeComponent from './TimeComponent.vue'
|
import TimeComponent from './TimeComponent.vue'
|
||||||
import Charts from './Charts.vue'
|
import Charts from './Charts.vue'
|
||||||
import AMap from './AMap.vue'
|
import PropertyAMap from './PropertyAMap.vue'
|
||||||
import Table from './Table.vue'
|
import Table from './Table.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<a-card :hoverable="true" class="card-box">
|
<a-card :hoverable="true" class="card-box">
|
||||||
<!-- <a-spin :spinning="loading"> -->
|
<!-- <a-spin :spinning="loading"> -->
|
||||||
<div class="card-container">
|
<div class="card-container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="title">{{ _props.data.name }}</div>
|
<div class="title">{{ _props.data.name }}</div>
|
||||||
<div class="extra">
|
<div class="extra">
|
||||||
<a-space :size="16">
|
<a-space :size="16">
|
||||||
|
<template v-for="i in actions" :key="i.key">
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-for="i in actions"
|
|
||||||
:key="i.key"
|
|
||||||
v-bind="i.tooltip"
|
v-bind="i.tooltip"
|
||||||
|
v-if="i.key !== 'edit'"
|
||||||
>
|
>
|
||||||
<a-button
|
<a-button
|
||||||
style="padding: 0; margin: 0"
|
style="padding: 0; margin: 0"
|
||||||
|
@ -17,26 +17,48 @@
|
||||||
:disabled="i.disabled"
|
:disabled="i.disabled"
|
||||||
@click="i.onClick && i.onClick(data)"
|
@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-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-space>
|
<PermissionButton
|
||||||
</div>
|
:disabled="i.disabled"
|
||||||
</div>
|
v-else
|
||||||
<div class="value">
|
:popConfirm="i.popConfirm"
|
||||||
<ValueRender :data="data" :value="_props.data" type="card" />
|
:tooltip="i.tooltip"
|
||||||
</div>
|
@click="i.onClick && i.onClick(slotProps)"
|
||||||
<div class="bottom">
|
type="link"
|
||||||
<div style="color: rgba(0, 0, 0, .65); font-size: 12px">更新时间</div>
|
style="padding: 0px"
|
||||||
<div class="time-value">{{_props?.data?.timeString || '--'}}</div>
|
:hasPermission="'device/Instance:update'"
|
||||||
|
>
|
||||||
|
<template #icon
|
||||||
|
><AIcon :type="i.icon" style="color: #323130; font-size: 12px"
|
||||||
|
/></template>
|
||||||
|
</PermissionButton>
|
||||||
|
</template>
|
||||||
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="value">
|
||||||
|
<ValueRender :data="data" :value="_props.data" type="card" />
|
||||||
|
</div>
|
||||||
|
<div class="bottom">
|
||||||
|
<div style="color: rgba(0, 0, 0, 0.65); font-size: 12px">
|
||||||
|
更新时间
|
||||||
|
</div>
|
||||||
|
<div class="time-value">
|
||||||
|
{{ _props?.data?.timeString || '--' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- </a-spin> -->
|
<!-- </a-spin> -->
|
||||||
</a-card>
|
</a-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import ValueRender from './ValueRender.vue'
|
import ValueRender from './ValueRender.vue';
|
||||||
const _props = defineProps({
|
const _props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -44,7 +66,7 @@ const _props = defineProps({
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// const loading = ref<boolean>(true);
|
// const loading = ref<boolean>(true);
|
||||||
|
@ -101,6 +123,6 @@ const _props = defineProps({
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -13,23 +13,18 @@
|
||||||
<a-image :src="value?.formatValue" />
|
<a-image :src="value?.formatValue" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="['.flv', '.m3u8', '.mp4'].includes(type)">
|
<template v-else-if="['.flv', '.m3u8', '.mp4'].includes(type)">
|
||||||
<!-- TODO 视频组件缺失 -->
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<!-- <json-viewer
|
<JsonViewer
|
||||||
:value="{
|
:expand-depth="5"
|
||||||
'id': '123'
|
:value="value?.formatValue"
|
||||||
}"
|
/>
|
||||||
copyable
|
|
||||||
boxed
|
|
||||||
sort
|
|
||||||
></json-viewer> -->
|
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// import JsonViewer from 'vue3-json-viewer';
|
import JsonViewer from 'vue-json-viewer';
|
||||||
|
|
||||||
const _data = defineProps({
|
const _data = defineProps({
|
||||||
type: {
|
type: {
|
||||||
|
@ -46,9 +41,6 @@ const handleCancel = () => {
|
||||||
_emit('close');
|
_emit('close');
|
||||||
};
|
};
|
||||||
|
|
||||||
// watchEffect(() => {
|
|
||||||
// console.log(_data.value?.formatValue)
|
|
||||||
// })
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
<div v-if="value?.formatValue !== 0 && !value?.formatValue" :class="valueClass">--</div>
|
<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'">
|
<template v-if="data?.valueType?.fileType === 'base64'">
|
||||||
<div :class="valueClass" v-if="!!getType(value?.formatValue)">
|
<div :class="valueClass" v-if="!!getType(value?.formatValue)">
|
||||||
<img :src="imgMap.get(_type)" @error="onError" />
|
<img :src="imgMap.get(_type)" @error="onError" />
|
||||||
|
@ -36,10 +36,10 @@
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</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')" />
|
<img :src="imgMap.get('obj')" />
|
||||||
</div>
|
</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)}}
|
{{JSON.stringify(value?.formatValue)}}
|
||||||
</div>
|
</div>
|
||||||
<div v-else :class="valueClass">
|
<div v-else :class="valueClass">
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
import { getImage } from "@/utils/comm";
|
import { getImage } from "@/utils/comm";
|
||||||
import { message } from "ant-design-vue";
|
import { message } from "ant-design-vue";
|
||||||
import ValueDetail from './ValueDetail.vue'
|
import ValueDetail from './ValueDetail.vue'
|
||||||
import {getType, imgMap} from './index'
|
import {getType, imgMap, imgList, videoList, fileList} from './index'
|
||||||
|
|
||||||
const _data = defineProps({
|
const _data = defineProps({
|
||||||
data: {
|
data: {
|
||||||
|
@ -115,7 +115,6 @@ const getDetail = (_type: string) => {
|
||||||
_types.value = flag
|
_types.value = flag
|
||||||
visible.value = true
|
visible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -32,20 +32,30 @@
|
||||||
</template>
|
</template>
|
||||||
<template #action="slotProps">
|
<template #action="slotProps">
|
||||||
<a-space :size="16">
|
<a-space :size="16">
|
||||||
<a-tooltip
|
<template v-for="i in getActions(slotProps)" :key="i.key">
|
||||||
v-for="i in getActions(slotProps)"
|
<a-tooltip v-bind="i.tooltip" v-if="i.key !== 'edit'">
|
||||||
:key="i.key"
|
<a-button
|
||||||
v-bind="i.tooltip"
|
style="padding: 0"
|
||||||
>
|
type="link"
|
||||||
<a-button
|
:disabled="i.disabled"
|
||||||
style="padding: 0"
|
@click="i.onClick && i.onClick(slotProps)"
|
||||||
type="link"
|
>
|
||||||
|
<AIcon :type="i.icon" />
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<PermissionButton
|
||||||
:disabled="i.disabled"
|
:disabled="i.disabled"
|
||||||
|
v-else
|
||||||
|
:popConfirm="i.popConfirm"
|
||||||
|
:tooltip="i.tooltip"
|
||||||
@click="i.onClick && i.onClick(slotProps)"
|
@click="i.onClick && i.onClick(slotProps)"
|
||||||
|
type="link"
|
||||||
|
style="padding: 0px"
|
||||||
|
:hasPermission="'device/Instance:update'"
|
||||||
>
|
>
|
||||||
<AIcon :type="i.icon" />
|
<template #icon><AIcon :type="i.icon" /></template>
|
||||||
</a-button>
|
</PermissionButton>
|
||||||
</a-tooltip>
|
</template>
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
<template #paginationRender>
|
<template #paginationRender>
|
||||||
|
@ -76,7 +86,11 @@
|
||||||
@close="indicatorVisible = false"
|
@close="indicatorVisible = false"
|
||||||
:data="currentInfo"
|
:data="currentInfo"
|
||||||
/>
|
/>
|
||||||
<Detail v-if="detailVisible" :data="currentInfo" @close="detailVisible = false" />
|
<Detail
|
||||||
|
v-if="detailVisible"
|
||||||
|
:data="currentInfo"
|
||||||
|
@close="detailVisible = false"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -240,11 +254,15 @@ const subscribeProperty = () => {
|
||||||
?.pipe(map((res: any) => res.payload))
|
?.pipe(map((res: any) => res.payload))
|
||||||
.subscribe((payload) => {
|
.subscribe((payload) => {
|
||||||
list.value = [...list.value, payload];
|
list.value = [...list.value, payload];
|
||||||
unref(list).sort((a: any, b: any) => a.timestamp - b.timestamp)
|
unref(list)
|
||||||
.forEach((item: any) => {
|
.sort((a: any, b: any) => a.timestamp - b.timestamp)
|
||||||
const { value } = item;
|
.forEach((item: any) => {
|
||||||
propertyValue.value[value?.property] = { ...item, ...value };
|
const { value } = item;
|
||||||
});
|
propertyValue.value[value?.property] = {
|
||||||
|
...item,
|
||||||
|
...value,
|
||||||
|
};
|
||||||
|
});
|
||||||
// list.value = [...list.value, payload];
|
// list.value = [...list.value, payload];
|
||||||
// throttle(valueChange(list.value), 500);
|
// throttle(valueChange(list.value), 500);
|
||||||
});
|
});
|
||||||
|
@ -337,8 +355,8 @@ const onSearch = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
subRef.value && subRef.value?.unsubscribe()
|
subRef.value && subRef.value?.unsubscribe();
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<page-container
|
<page-container
|
||||||
:tabList="list"
|
:tabList="list"
|
||||||
@back="onBack"
|
@back="onBack"
|
||||||
:tabActiveKey="instanceStore.active"
|
:tabActiveKey="instanceStore.tabActiveKey"
|
||||||
@tabChange="onTabChange"
|
@tabChange="onTabChange"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
|
|
|
@ -155,13 +155,12 @@
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<h3
|
<Ellipsis style="width: calc(100% - 100px)">
|
||||||
class="card-item-content-title"
|
<span style="font-size: 16px; font-weight: 600" @click.stop="handleView(slotProps.id)">
|
||||||
@click.stop="handleView(slotProps.id)"
|
{{ slotProps.name }}
|
||||||
>
|
</span>
|
||||||
{{ slotProps.name }}
|
</Ellipsis>
|
||||||
</h3>
|
<a-row style="margin-top: 20px">
|
||||||
<a-row>
|
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<div class="card-item-content-text">
|
<div class="card-item-content-text">
|
||||||
设备类型
|
设备类型
|
||||||
|
@ -172,7 +171,9 @@
|
||||||
<div class="card-item-content-text">
|
<div class="card-item-content-text">
|
||||||
产品名称
|
产品名称
|
||||||
</div>
|
</div>
|
||||||
<div>{{ slotProps.productName }}</div>
|
<Ellipsis style="width: 100%">
|
||||||
|
{{ slotProps.productName }}
|
||||||
|
</Ellipsis>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,167 +1,180 @@
|
||||||
<template>
|
<template>
|
||||||
<page-container>
|
<page-container>
|
||||||
<Search
|
<Search
|
||||||
:columns="query.columns"
|
:columns="query.columns"
|
||||||
target="product-manage"
|
target="product-manage"
|
||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
/>
|
/>
|
||||||
<JTable
|
<JTable
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:request="queryProductList"
|
:request="queryProductList"
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:defaultParams="{
|
:defaultParams="{
|
||||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
}"
|
}"
|
||||||
:params="params"
|
:params="params"
|
||||||
>
|
>
|
||||||
<template #headerTitle>
|
<template #headerTitle>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button type="primary" @click="add"
|
<a-button type="primary" @click="add"
|
||||||
><plus-outlined />新增</a-button
|
><plus-outlined />新增</a-button
|
||||||
>
|
|
||||||
<a-upload
|
|
||||||
name="file"
|
|
||||||
accept=".json"
|
|
||||||
:showUploadList="false"
|
|
||||||
:before-upload="beforeUpload"
|
|
||||||
>
|
|
||||||
<a-button>导入</a-button>
|
|
||||||
</a-upload>
|
|
||||||
</a-space>
|
|
||||||
</template>
|
|
||||||
<template #deviceType="slotProps">
|
|
||||||
<div>{{ slotProps.deviceType.text }}</div>
|
|
||||||
</template>
|
|
||||||
<template #card="slotProps">
|
|
||||||
<CardBox
|
|
||||||
:value="slotProps"
|
|
||||||
@click="handleClick"
|
|
||||||
:actions="getActions(slotProps, 'card')"
|
|
||||||
v-bind="slotProps"
|
|
||||||
:active="_selectedRowKeys.includes(slotProps.id)"
|
|
||||||
:status="slotProps.state"
|
|
||||||
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
|
||||||
:statusNames="{
|
|
||||||
1: 'success',
|
|
||||||
0: 'error',
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
<template #img>
|
<a-upload
|
||||||
<slot name="img">
|
name="file"
|
||||||
<img :src="getImage('/device-product.png')" />
|
accept=".json"
|
||||||
</slot>
|
:showUploadList="false"
|
||||||
</template>
|
:before-upload="beforeUpload"
|
||||||
<template #content>
|
>
|
||||||
<h3
|
<a-button>导入</a-button>
|
||||||
|
</a-upload>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<template #deviceType="slotProps">
|
||||||
|
<div>{{ slotProps.deviceType.text }}</div>
|
||||||
|
</template>
|
||||||
|
<template #card="slotProps">
|
||||||
|
<CardBox
|
||||||
|
:value="slotProps"
|
||||||
|
@click="handleClick"
|
||||||
|
:actions="getActions(slotProps, 'card')"
|
||||||
|
v-bind="slotProps"
|
||||||
|
:active="_selectedRowKeys.includes(slotProps.id)"
|
||||||
|
:status="slotProps.state"
|
||||||
|
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
||||||
|
:statusNames="{
|
||||||
|
1: 'success',
|
||||||
|
0: 'error',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #img>
|
||||||
|
<slot name="img">
|
||||||
|
<img
|
||||||
|
:src="
|
||||||
|
slotProps.photoUrl ||
|
||||||
|
getImage('/device-product.png')
|
||||||
|
"
|
||||||
|
class="productImg"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<Ellipsis
|
||||||
|
><span
|
||||||
@click.stop="handleView(slotProps.id)"
|
@click.stop="handleView(slotProps.id)"
|
||||||
style="font-weight: 600"
|
style="font-weight: 600; font-size: 16px"
|
||||||
>
|
>
|
||||||
{{ slotProps.name }}
|
{{ slotProps.name }}
|
||||||
</h3>
|
</span></Ellipsis
|
||||||
<a-row>
|
>
|
||||||
<a-col :span="12">
|
<a-row>
|
||||||
<div class="card-item-content-text">
|
<a-col :span="12">
|
||||||
设备类型
|
<div class="card-item-content-text">
|
||||||
</div>
|
设备类型
|
||||||
<div>直连设备</div>
|
</div>
|
||||||
</a-col>
|
<div>{{ slotProps?.deviceType?.text }}</div>
|
||||||
</a-row>
|
</a-col>
|
||||||
</template>
|
<a-col :span="12">
|
||||||
<template #actions="item">
|
<div class="card-item-content-text">
|
||||||
<a-tooltip
|
接入方式
|
||||||
v-bind="item.tooltip"
|
</div>
|
||||||
:title="item.disabled && item.tooltip.title"
|
<Ellipsis
|
||||||
>
|
><div>
|
||||||
<a-popconfirm
|
{{ slotProps?.accessName }}
|
||||||
v-if="item.popConfirm"
|
</div></Ellipsis
|
||||||
v-bind="item.popConfirm"
|
|
||||||
:disabled="item.disabled"
|
|
||||||
okText="确定"
|
|
||||||
cancelText="取消"
|
|
||||||
>
|
>
|
||||||
<a-button :disabled="item.disabled">
|
</a-col>
|
||||||
<AIcon
|
</a-row>
|
||||||
type="DeleteOutlined"
|
</template>
|
||||||
v-if="item.key === 'delete'"
|
<template #actions="item">
|
||||||
/>
|
|
||||||
<template v-else>
|
|
||||||
<AIcon :type="item.icon" />
|
|
||||||
<span>{{ item?.text }}</span>
|
|
||||||
</template>
|
|
||||||
</a-button>
|
|
||||||
</a-popconfirm>
|
|
||||||
<template v-else>
|
|
||||||
<a-button
|
|
||||||
:disabled="item.disabled"
|
|
||||||
@click="item.onClick"
|
|
||||||
>
|
|
||||||
<AIcon
|
|
||||||
type="DeleteOutlined"
|
|
||||||
v-if="item.key === 'delete'"
|
|
||||||
/>
|
|
||||||
<template v-else>
|
|
||||||
<AIcon :type="item.icon" />
|
|
||||||
<span>{{ item?.text }}</span>
|
|
||||||
</template>
|
|
||||||
</a-button>
|
|
||||||
</template>
|
|
||||||
</a-tooltip>
|
|
||||||
</template>
|
|
||||||
</CardBox>
|
|
||||||
</template>
|
|
||||||
<template #state="slotProps">
|
|
||||||
<a-badge
|
|
||||||
:text="slotProps.state === 1 ? '正常' : '禁用'"
|
|
||||||
:status="statusMap.get(slotProps.state)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template #id="slotProps">
|
|
||||||
<a>{{ slotProps.id }}</a>
|
|
||||||
</template>
|
|
||||||
<template #action="slotProps">
|
|
||||||
<a-space :size="16">
|
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-for="i in getActions(slotProps)"
|
v-bind="item.tooltip"
|
||||||
:key="i.key"
|
:title="item.disabled && item.tooltip.title"
|
||||||
v-bind="i.tooltip"
|
|
||||||
>
|
>
|
||||||
<a-popconfirm
|
<a-popconfirm
|
||||||
v-if="i.popConfirm"
|
v-if="item.popConfirm"
|
||||||
v-bind="i.popConfirm"
|
v-bind="item.popConfirm"
|
||||||
|
:disabled="item.disabled"
|
||||||
okText="确定"
|
okText="确定"
|
||||||
cancelText="取消"
|
cancelText="取消"
|
||||||
>
|
>
|
||||||
<a-button
|
<a-button :disabled="item.disabled">
|
||||||
:disabled="i.disabled"
|
<AIcon
|
||||||
style="padding: 0"
|
type="DeleteOutlined"
|
||||||
type="link"
|
v-if="item.key === 'delete'"
|
||||||
><AIcon :type="i.icon"
|
/>
|
||||||
/></a-button>
|
<template v-else>
|
||||||
|
<AIcon :type="item.icon" />
|
||||||
|
<span>{{ item?.text }}</span>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
|
<template v-else>
|
||||||
|
<a-button
|
||||||
|
:disabled="item.disabled"
|
||||||
|
@click="item.onClick"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="DeleteOutlined"
|
||||||
|
v-if="item.key === 'delete'"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<AIcon :type="item.icon" />
|
||||||
|
<span>{{ item?.text }}</span>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</CardBox>
|
||||||
|
</template>
|
||||||
|
<template #state="slotProps">
|
||||||
|
<a-badge
|
||||||
|
:text="slotProps.state === 1 ? '正常' : '禁用'"
|
||||||
|
:status="statusMap.get(slotProps.state)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #id="slotProps">
|
||||||
|
<a>{{ slotProps.id }}</a>
|
||||||
|
</template>
|
||||||
|
<template #action="slotProps">
|
||||||
|
<a-space :size="16">
|
||||||
|
<a-tooltip
|
||||||
|
v-for="i in getActions(slotProps)"
|
||||||
|
:key="i.key"
|
||||||
|
v-bind="i.tooltip"
|
||||||
|
>
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="i.popConfirm"
|
||||||
|
v-bind="i.popConfirm"
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
>
|
||||||
<a-button
|
<a-button
|
||||||
|
:disabled="i.disabled"
|
||||||
style="padding: 0"
|
style="padding: 0"
|
||||||
type="link"
|
type="link"
|
||||||
v-else
|
><AIcon :type="i.icon"
|
||||||
@click="i.onClick && i.onClick(slotProps)"
|
/></a-button>
|
||||||
>
|
</a-popconfirm>
|
||||||
<a-button
|
<a-button
|
||||||
:disabled="i.disabled"
|
style="padding: 0"
|
||||||
style="padding: 0"
|
type="link"
|
||||||
type="link"
|
v-else
|
||||||
><AIcon :type="i.icon"
|
@click="i.onClick && i.onClick(slotProps)"
|
||||||
/></a-button>
|
>
|
||||||
</a-button>
|
<a-button
|
||||||
</a-tooltip>
|
:disabled="i.disabled"
|
||||||
</a-space>
|
style="padding: 0"
|
||||||
</template>
|
type="link"
|
||||||
</JTable>
|
><AIcon :type="i.icon"
|
||||||
<!-- 新增、编辑 -->
|
/></a-button>
|
||||||
<Save
|
</a-button>
|
||||||
ref="saveRef"
|
</a-tooltip>
|
||||||
:isAdd="isAdd"
|
</a-space>
|
||||||
:title="title"
|
</template>
|
||||||
@success="refresh"
|
</JTable>
|
||||||
/>
|
<!-- 新增、编辑 -->
|
||||||
|
<Save ref="saveRef" :isAdd="isAdd" :title="title" @success="refresh" />
|
||||||
</page-container>
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -193,11 +206,11 @@ import { isNoCommunity, downloadObject } from '@/utils/utils';
|
||||||
import { omit } from 'lodash-es';
|
import { omit } from 'lodash-es';
|
||||||
import { typeOptions } from '@/components/Search/util';
|
import { typeOptions } from '@/components/Search/util';
|
||||||
import Save from './Save/index.vue';
|
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 router = useRouter();
|
||||||
const isAdd = ref<number>(0);
|
const isAdd = ref<number>(0);
|
||||||
const title = ref<string>('');
|
const title = ref<string>('');
|
||||||
|
@ -425,7 +438,7 @@ const beforeUpload = (file: any) => {
|
||||||
* 查看
|
* 查看
|
||||||
*/
|
*/
|
||||||
const handleView = (id: string) => {
|
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;
|
padding: 20px;
|
||||||
background: #f0f2f5;
|
background: #f0f2f5;
|
||||||
}
|
}
|
||||||
|
.productImg {
|
||||||
|
width: 88px;
|
||||||
|
height: 88px;
|
||||||
|
}
|
||||||
|
.productName {
|
||||||
|
white-space: nowrap; /*强制在同一行内显示所有文本,直到文本结束或者遭遇br标签对象才换行。*/
|
||||||
|
overflow: hidden; /*超出部分隐藏*/
|
||||||
|
text-overflow: ellipsis; /*隐藏部分以省略号代替*/
|
||||||
|
}
|
||||||
</style>
|
</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"
|
v-bind="validateInfos.productId"
|
||||||
>
|
>
|
||||||
<a-row :gutter="[0, 10]">
|
<a-row :gutter="[0, 10]">
|
||||||
<a-col :span="22">
|
<a-col :span="!!route.query.id ? 24 : 22">
|
||||||
<a-select
|
<a-select
|
||||||
v-model:value="formData.productId"
|
v-model:value="formData.productId"
|
||||||
placeholder="请选择所属产品"
|
placeholder="请选择所属产品"
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="2">
|
<a-col :span="2" v-if="!route.query.id">
|
||||||
<a-button
|
<a-button
|
||||||
type="link"
|
type="link"
|
||||||
@click="saveProductVis = true"
|
@click="saveProductVis = true"
|
||||||
|
@ -132,12 +132,11 @@
|
||||||
placeholder="请输入说明"
|
placeholder="请输入说明"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{ offset: 0, span: 3 }">
|
<a-form-item>
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handleSubmit"
|
@click="handleSubmit"
|
||||||
:loading="btnLoading"
|
:loading="btnLoading"
|
||||||
style="width: 100%"
|
|
||||||
>
|
>
|
||||||
保存
|
保存
|
||||||
</a-button>
|
</a-button>
|
||||||
|
@ -356,6 +355,8 @@ const getDetail = async () => {
|
||||||
// formData.value = res.result;
|
// formData.value = res.result;
|
||||||
Object.assign(formData.value, res.result);
|
Object.assign(formData.value, res.result);
|
||||||
formData.value.channel = res.result.provider;
|
formData.value.channel = res.result.provider;
|
||||||
|
|
||||||
|
console.log('formData.value: ', formData.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
@ -367,6 +368,7 @@ onMounted(() => {
|
||||||
*/
|
*/
|
||||||
const btnLoading = ref<boolean>(false);
|
const btnLoading = ref<boolean>(false);
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
|
// console.log('formData.value: ', formData.value);
|
||||||
validate()
|
validate()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
btnLoading.value = true;
|
btnLoading.value = true;
|
||||||
|
|
|
@ -261,9 +261,13 @@ const getActions = (
|
||||||
},
|
},
|
||||||
icon: 'EditOutlined',
|
icon: 'EditOutlined',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
menuStory.jumpPage('media/Device/Save', {
|
menuStory.jumpPage(
|
||||||
id: data.id,
|
'media/Device/Save',
|
||||||
});
|
{},
|
||||||
|
{
|
||||||
|
id: data.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -277,10 +281,14 @@ const getActions = (
|
||||||
// router.push(
|
// router.push(
|
||||||
// `/media/device/Channel?id=${data.id}&type=${data.provider}`,
|
// `/media/device/Channel?id=${data.id}&type=${data.provider}`,
|
||||||
// );
|
// );
|
||||||
menuStory.jumpPage('media/Device/Channel', {
|
menuStory.jumpPage(
|
||||||
id: data.id,
|
'media/Device/Channel',
|
||||||
type: data.provider,
|
{},
|
||||||
});
|
{
|
||||||
|
id: data.id,
|
||||||
|
type: data.provider,
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -333,23 +333,9 @@ watch(
|
||||||
msgType.value = MSG_TYPE[val];
|
msgType.value = MSG_TYPE[val];
|
||||||
|
|
||||||
formData.value.provider =
|
formData.value.provider =
|
||||||
formData.value.provider !== ':id'
|
route.params.id !== ':id'
|
||||||
? formData.value.provider
|
? formData.value.provider
|
||||||
: msgType.value[0].value;
|
: 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,
|
formRules.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
const clearValid = () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
clearValidate();
|
|
||||||
}, 200);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDetail = async () => {
|
const getDetail = async () => {
|
||||||
if (route.params.id === ':id') return;
|
if (route.params.id === ':id') return;
|
||||||
const res = await configApi.detail(route.params.id as string);
|
const res = await configApi.detail(route.params.id as string);
|
||||||
|
@ -444,7 +424,7 @@ const handleTypeChange = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
formData.value.configuration =
|
formData.value.configuration =
|
||||||
CONFIG_FIELD_MAP[formData.value.type][formData.value.provider];
|
CONFIG_FIELD_MAP[formData.value.type][formData.value.provider];
|
||||||
// resetPublicFiles();
|
resetPublicFiles();
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -454,7 +434,48 @@ const handleTypeChange = () => {
|
||||||
const handleProviderChange = () => {
|
const handleProviderChange = () => {
|
||||||
formData.value.configuration =
|
formData.value.configuration =
|
||||||
CONFIG_FIELD_MAP[formData.value.type][formData.value.provider];
|
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"
|
@cancel="_vis = false"
|
||||||
width="80%"
|
width="80%"
|
||||||
>
|
>
|
||||||
<a-row :gutter="10">
|
<a-row :gutter="10" class="model-body">
|
||||||
<a-col :span="4">
|
<a-col :span="4">
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="deptName"
|
v-model:value="deptName"
|
||||||
|
@ -40,6 +40,7 @@
|
||||||
:dataSource="dataSource"
|
:dataSource="dataSource"
|
||||||
:loading="tableLoading"
|
:loading="tableLoading"
|
||||||
model="table"
|
model="table"
|
||||||
|
noPagination
|
||||||
>
|
>
|
||||||
<template #headerTitle>
|
<template #headerTitle>
|
||||||
<a-button type="primary" @click="handleAutoBind">
|
<a-button type="primary" @click="handleAutoBind">
|
||||||
|
@ -273,14 +274,24 @@ const getActions = (
|
||||||
* 自动绑定
|
* 自动绑定
|
||||||
*/
|
*/
|
||||||
const handleAutoBind = () => {
|
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('操作成功');
|
message.success('操作成功');
|
||||||
getTableData();
|
getTableData();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取钉钉部门用户
|
* 获取钉钉/微信部门用户
|
||||||
*/
|
*/
|
||||||
const getDeptUsers = async () => {
|
const getDeptUsers = async () => {
|
||||||
let res = null;
|
let res = null;
|
||||||
|
@ -304,14 +315,24 @@ const getBindUsers = async () => {
|
||||||
return res?.result;
|
return res?.result;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 获取所有用户
|
* 获取所有用户未绑定的用户
|
||||||
*/
|
*/
|
||||||
const allUserList = ref([]);
|
const allUserList = ref([]);
|
||||||
const getAllUsers = async () => {
|
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) => ({
|
allUserList.value = result.map((m: any) => ({
|
||||||
label: m.name,
|
label: m.name,
|
||||||
value: m.id,
|
value: m.id,
|
||||||
|
...m,
|
||||||
}));
|
}));
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
@ -326,31 +347,36 @@ const getTableData = () => {
|
||||||
Promise.all<any>([getDeptUsers(), getBindUsers(), getAllUsers()]).then(
|
Promise.all<any>([getDeptUsers(), getBindUsers(), getAllUsers()]).then(
|
||||||
(res) => {
|
(res) => {
|
||||||
dataSource.value = [];
|
dataSource.value = [];
|
||||||
const [deptUsers, bindUsers, allUsers] = res;
|
const [deptUsers, bindUsers, unBindUsers] = res;
|
||||||
(deptUsers || []).forEach((item: any) => {
|
(deptUsers || []).forEach((deptUser: any) => {
|
||||||
|
// 未绑定的用户
|
||||||
|
let unBindUser = unBindUsers.find(
|
||||||
|
(f: any) => f.name === deptUser?.name,
|
||||||
|
);
|
||||||
// 绑定的用户
|
// 绑定的用户
|
||||||
const bindUser = bindUsers.find(
|
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({
|
dataSource.value.push({
|
||||||
thirdPartyUserId: item.id,
|
thirdPartyUserId: deptUser.id,
|
||||||
thirdPartyUserName: item.name,
|
thirdPartyUserName: deptUser.name,
|
||||||
userId: bindUser?.userId,
|
bindId: bindUser?.userId,
|
||||||
userName: allUser
|
userId: unBindUser?.id,
|
||||||
? `${allUser.name}(${allUser.username})`
|
userName: unBindUser
|
||||||
|
? `${unBindUser.name}(${unBindUser.username})`
|
||||||
: '',
|
: '',
|
||||||
status: {
|
status: {
|
||||||
text: bindUser?.providerName ? '已绑定' : '未绑定',
|
text: bindUser?.providerName ? '已绑定' : '未绑定',
|
||||||
value: bindUser?.providerName ? 'success' : 'error',
|
value: bindUser?.providerName ? 'success' : 'error',
|
||||||
},
|
},
|
||||||
bindId: bindUser?.id,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
console.log('dataSource.value: ', dataSource.value);
|
// console.log('dataSource.value: ', dataSource.value);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
tableLoading.value = false;
|
tableLoading.value = false;
|
||||||
|
@ -369,7 +395,11 @@ watch(
|
||||||
*/
|
*/
|
||||||
const bindVis = ref(false);
|
const bindVis = ref(false);
|
||||||
const confirmLoading = ref(false);
|
const confirmLoading = ref(false);
|
||||||
const formData = ref({ userId: '' });
|
const formData = ref({
|
||||||
|
userId: '',
|
||||||
|
thirdPartyUserId: '',
|
||||||
|
thirdPartyUserName: '',
|
||||||
|
});
|
||||||
const formRules = ref({
|
const formRules = ref({
|
||||||
userId: [{ required: true, message: '请选择用户', trigger: 'change' }],
|
userId: [{ required: true, message: '请选择用户', trigger: 'change' }],
|
||||||
});
|
});
|
||||||
|
@ -381,7 +411,8 @@ const { resetFields, validate, validateInfos, clearValidate } = useForm(
|
||||||
|
|
||||||
const handleBind = (row: any) => {
|
const handleBind = (row: any) => {
|
||||||
bindVis.value = true;
|
bindVis.value = true;
|
||||||
formData.value = row;
|
// formData.value = row;
|
||||||
|
Object.assign(formData.value, row);
|
||||||
getAllUsers();
|
getAllUsers();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -402,8 +433,8 @@ const filterOption = (input: string, option: any) => {
|
||||||
const handleBindSubmit = () => {
|
const handleBindSubmit = () => {
|
||||||
validate().then(async () => {
|
validate().then(async () => {
|
||||||
const params = {
|
const params = {
|
||||||
// providerName: formData.value.thirdPartyUserName,
|
providerName: formData.value.thirdPartyUserName,
|
||||||
// thirdPartyUserId: formData.value.thirdPartyUserId,
|
thirdPartyUserId: formData.value.thirdPartyUserId,
|
||||||
userId: formData.value.userId,
|
userId: formData.value.userId,
|
||||||
};
|
};
|
||||||
confirmLoading.value = true;
|
confirmLoading.value = true;
|
||||||
|
@ -434,8 +465,13 @@ const handleBindSubmit = () => {
|
||||||
};
|
};
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
bindVis.value = false;
|
bindVis.value = false;
|
||||||
resetFields()
|
resetFields();
|
||||||
};
|
};
|
||||||
</script>
|
</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-button>导入</a-button>
|
||||||
</a-upload>
|
</a-upload>
|
||||||
<a-popconfirm
|
<a-popconfirm
|
||||||
title="确认导出当前页数据?"
|
title="确认导出?"
|
||||||
ok-text="确定"
|
ok-text="确定"
|
||||||
cancel-text="取消"
|
cancel-text="取消"
|
||||||
@confirm="handleExport"
|
@confirm="handleExport"
|
||||||
|
@ -308,7 +308,7 @@ const beforeUpload = (file: any) => {
|
||||||
* 导出
|
* 导出
|
||||||
*/
|
*/
|
||||||
const handleExport = () => {
|
const handleExport = () => {
|
||||||
downloadObject(configRef.value.dataSource, `通知配置`);
|
downloadObject(configRef.value._dataSource, `通知配置`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const syncVis = ref(false);
|
const syncVis = ref(false);
|
||||||
|
|
|
@ -92,10 +92,7 @@ const handleChange = (info: UploadChangeParam, id: string | undefined) => {
|
||||||
const targetFileIdx = fileList.value.findIndex((f) => f.id === id);
|
const targetFileIdx = fileList.value.findIndex((f) => f.id === id);
|
||||||
fileList.value[targetFileIdx].name = info.file.name;
|
fileList.value[targetFileIdx].name = info.file.name;
|
||||||
fileList.value[targetFileIdx].location = info.file.response?.result;
|
fileList.value[targetFileIdx].location = info.file.response?.result;
|
||||||
emit(
|
emitEvents();
|
||||||
'update:attachments',
|
|
||||||
fileList.value.map(({ name, location }) => ({ name, location })),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -107,6 +104,7 @@ const handleDelete = (id: string | undefined) => {
|
||||||
const idx = fileList.value.findIndex((f) => f.id === id);
|
const idx = fileList.value.findIndex((f) => f.id === id);
|
||||||
|
|
||||||
fileList.value.splice(idx, 1);
|
fileList.value.splice(idx, 1);
|
||||||
|
emitEvents();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,6 +116,15 @@ const handleAdd = () => {
|
||||||
name: '',
|
name: '',
|
||||||
location: '',
|
location: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
emitEvents();
|
||||||
|
};
|
||||||
|
|
||||||
|
const emitEvents = () => {
|
||||||
|
emit(
|
||||||
|
'update:attachments',
|
||||||
|
fileList.value.map(({ name, location }) => ({ name, location })),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -106,7 +106,8 @@
|
||||||
<a-form-item label="收信部门">
|
<a-form-item label="收信部门">
|
||||||
<ToOrg
|
<ToOrg
|
||||||
v-model:toParty="
|
v-model:toParty="
|
||||||
formData.template.toParty
|
formData.template
|
||||||
|
.departmentIdList
|
||||||
"
|
"
|
||||||
:type="formData.type"
|
:type="formData.type"
|
||||||
:config-id="formData.configId"
|
:config-id="formData.configId"
|
||||||
|
@ -132,7 +133,7 @@
|
||||||
</template>
|
</template>
|
||||||
<ToUser
|
<ToUser
|
||||||
v-model:toUser="
|
v-model:toUser="
|
||||||
formData.template.toUser
|
formData.template.userIdList
|
||||||
"
|
"
|
||||||
:type="formData.type"
|
:type="formData.type"
|
||||||
:config-id="formData.configId"
|
:config-id="formData.configId"
|
||||||
|
@ -800,26 +801,59 @@ const formData = ref<TemplateFormData>({
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置公用字段值
|
* 重置字段值
|
||||||
*/
|
*/
|
||||||
const resetPublicFiles = () => {
|
const resetPublicFiles = () => {
|
||||||
formData.value.template.message = '';
|
switch (formData.value.provider) {
|
||||||
formData.value.configId = undefined;
|
case 'dingTalkMessage':
|
||||||
|
formData.value.template.agentId = '';
|
||||||
if (
|
formData.value.template.message = '';
|
||||||
formData.value.provider === 'dingTalkMessage' ||
|
formData.value.template.departmentIdList = '';
|
||||||
formData.value.type === 'weixin'
|
formData.value.template.userIdList = '';
|
||||||
) {
|
break;
|
||||||
formData.value.template.toTag = undefined;
|
case 'dingTalkRobotWebHook':
|
||||||
formData.value.template.toUser = undefined;
|
formData.value.template.message = '';
|
||||||
formData.value.template.agentId = undefined;
|
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;
|
formData.value.configId = undefined;
|
||||||
if (formData.value.type === 'email')
|
|
||||||
formData.value.template.toParty = undefined;
|
|
||||||
// formData.value.description = '';
|
|
||||||
formData.value.variableDefinitions = [];
|
formData.value.variableDefinitions = [];
|
||||||
|
handleMessageTypeChange();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 根据通知方式展示对应的字段
|
// 根据通知方式展示对应的字段
|
||||||
|
@ -831,15 +865,8 @@ watch(
|
||||||
route.params.id !== ':id'
|
route.params.id !== ':id'
|
||||||
? formData.value.provider
|
? formData.value.provider
|
||||||
: msgType.value[0].value;
|
: 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();
|
if (val !== 'email') getConfigList();
|
||||||
// clearValid();
|
|
||||||
// console.log('formData.value: ', formData.value);
|
|
||||||
|
|
||||||
if (val === 'sms') {
|
if (val === 'sms') {
|
||||||
getTemplateList();
|
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({
|
const formRules = ref({
|
||||||
type: [{ required: true, message: '请选择通知方式' }],
|
type: [{ required: true, message: '请选择通知方式' }],
|
||||||
|
@ -917,7 +935,7 @@ watch(
|
||||||
() => formData.value.template.markdown?.title,
|
() => formData.value.template.markdown?.title,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (!val) return;
|
if (!val) return;
|
||||||
variableReg(val);
|
variableReg();
|
||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
);
|
);
|
||||||
|
@ -926,7 +944,7 @@ watch(
|
||||||
() => formData.value.template.link?.title,
|
() => formData.value.template.link?.title,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (!val) return;
|
if (!val) return;
|
||||||
variableReg(val);
|
variableReg();
|
||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
);
|
);
|
||||||
|
@ -935,7 +953,7 @@ watch(
|
||||||
() => formData.value.template.subject,
|
() => formData.value.template.subject,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (!val) return;
|
if (!val) return;
|
||||||
variableReg(val);
|
variableReg();
|
||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
);
|
);
|
||||||
|
@ -945,7 +963,7 @@ watch(
|
||||||
() => formData.value.template.message,
|
() => formData.value.template.message,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (!val) return;
|
if (!val) return;
|
||||||
variableReg(val);
|
variableReg();
|
||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
);
|
);
|
||||||
|
@ -954,21 +972,42 @@ watch(
|
||||||
() => formData.value.template.body,
|
() => formData.value.template.body,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (!val) return;
|
if (!val) return;
|
||||||
variableReg(val);
|
variableReg();
|
||||||
},
|
},
|
||||||
{ deep: true },
|
{ 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
|
* @param value
|
||||||
*/
|
*/
|
||||||
const variableReg = (value: string) => {
|
const variableReg = () => {
|
||||||
|
const _val = spliceStr();
|
||||||
// 已经存在的变量
|
// 已经存在的变量
|
||||||
const oldKey = formData.value.variableDefinitions?.map((m) => m.id);
|
const oldKey = formData.value.variableDefinitions?.map((m) => m.id);
|
||||||
// 正则提取${}里面的值
|
// 正则提取${}里面的值
|
||||||
const pattern = /(?<=\$\{).*?(?=\})/g;
|
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 newKey = [...new Set(titleList)];
|
||||||
const result = newKey?.map((m) =>
|
const result = newKey?.map((m) =>
|
||||||
oldKey.includes(m)
|
oldKey.includes(m)
|
||||||
|
@ -980,28 +1019,37 @@ const variableReg = (value: string) => {
|
||||||
format: '%s',
|
format: '%s',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
formData.value.variableDefinitions = [
|
formData.value.variableDefinitions = result as IVariableDefinitions[];
|
||||||
...new Set([
|
|
||||||
...formData.value.variableDefinitions,
|
|
||||||
...(result as IVariableDefinitions[]),
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 钉钉机器人 消息类型选择改变
|
* 钉钉机器人 消息类型选择改变
|
||||||
*/
|
*/
|
||||||
const handleMessageTypeChange = () => {
|
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.variableDefinitions = [];
|
||||||
formData.value.template.message = '';
|
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(() => {
|
setTimeout(() => {
|
||||||
formData.value.template =
|
formData.value.template =
|
||||||
TEMPLATE_FIELD_MAP[formData.value.type][formData.value.provider];
|
TEMPLATE_FIELD_MAP[formData.value.type][formData.value.provider];
|
||||||
|
// console.log('formData.value.template: ', formData.value.template);
|
||||||
resetPublicFiles();
|
resetPublicFiles();
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
|
@ -1047,6 +1096,8 @@ const handleTypeChange = () => {
|
||||||
const handleProviderChange = () => {
|
const handleProviderChange = () => {
|
||||||
formData.value.template =
|
formData.value.template =
|
||||||
TEMPLATE_FIELD_MAP[formData.value.type][formData.value.provider];
|
TEMPLATE_FIELD_MAP[formData.value.type][formData.value.provider];
|
||||||
|
// console.log('formData.value: ', formData.value);
|
||||||
|
// console.log('formData.value.template: ', formData.value.template);
|
||||||
getConfigList();
|
getConfigList();
|
||||||
resetPublicFiles();
|
resetPublicFiles();
|
||||||
};
|
};
|
||||||
|
@ -1112,8 +1163,9 @@ const handleSubmit = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
validate()
|
validate()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
formData.value.template.ttsCode =
|
if (formData.value.provider === 'ttsCode')
|
||||||
formData.value.template.templateCode;
|
formData.value.template.ttsCode =
|
||||||
|
formData.value.template.templateCode;
|
||||||
btnLoading.value = true;
|
btnLoading.value = true;
|
||||||
let res;
|
let res;
|
||||||
if (!formData.value.id) {
|
if (!formData.value.id) {
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<a-button>导入</a-button>
|
<a-button>导入</a-button>
|
||||||
</a-upload>
|
</a-upload>
|
||||||
<a-popconfirm
|
<a-popconfirm
|
||||||
title="确认导出当前页数据?"
|
title="确认导出?"
|
||||||
ok-text="确定"
|
ok-text="确定"
|
||||||
cancel-text="取消"
|
cancel-text="取消"
|
||||||
@confirm="handleExport"
|
@confirm="handleExport"
|
||||||
|
@ -314,7 +314,7 @@ const beforeUpload = (file: any) => {
|
||||||
* 导出
|
* 导出
|
||||||
*/
|
*/
|
||||||
const handleExport = () => {
|
const handleExport = () => {
|
||||||
downloadObject(configRef.value.dataSource, `通知配置`);
|
downloadObject(configRef.value._dataSource, `通知配置`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const syncVis = ref(false);
|
const syncVis = ref(false);
|
||||||
|
|
|
@ -27,16 +27,22 @@ interface ILink {
|
||||||
messageUrl: string;
|
messageUrl: string;
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
interface IText {
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type TemplateFormData = {
|
export type TemplateFormData = {
|
||||||
template: {
|
template: {
|
||||||
// 钉钉消息
|
// 钉钉消息
|
||||||
agentId?: string;
|
agentId?: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
|
departmentIdList?: string;
|
||||||
|
userIdList?: string;
|
||||||
// 钉钉机器人
|
// 钉钉机器人
|
||||||
messageType?: string;
|
messageType?: string;
|
||||||
markdown?: IMarkDown;
|
markdown?: IMarkDown;
|
||||||
link?: ILink;
|
link?: ILink;
|
||||||
|
text?: IText;
|
||||||
// 微信
|
// 微信
|
||||||
// agentId?: string;
|
// agentId?: string;
|
||||||
// message?: string;
|
// message?: string;
|
||||||
|
|
|
@ -147,10 +147,12 @@ export const TEMPLATE_FIELD_MAP = {
|
||||||
dingTalkMessage: {
|
dingTalkMessage: {
|
||||||
agentId: '',
|
agentId: '',
|
||||||
message: '',
|
message: '',
|
||||||
|
departmentIdList: '',
|
||||||
|
userIdList: ''
|
||||||
},
|
},
|
||||||
dingTalkRobotWebHook: {
|
dingTalkRobotWebHook: {
|
||||||
message: '',
|
message: '',
|
||||||
messageType: '',
|
messageType: 'markdown',
|
||||||
markdown: {
|
markdown: {
|
||||||
text: '',
|
text: '',
|
||||||
title: '',
|
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>
|
<page-container>
|
||||||
<div>
|
<div>
|
||||||
<Search
|
<Search
|
||||||
:columns="query.columns"
|
:columns="columns"
|
||||||
target="device-instance"
|
target="device-instance"
|
||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
></Search>
|
></Search>
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
:defaultParams="{
|
:defaultParams="{
|
||||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
}"
|
}"
|
||||||
|
:params="params"
|
||||||
>
|
>
|
||||||
<template #headerTitle>
|
<template #headerTitle>
|
||||||
<a-space>
|
<a-space>
|
||||||
|
@ -41,23 +42,27 @@
|
||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<h3 style="font-weight: 600">
|
<Ellipsis>
|
||||||
{{ slotProps.name }}
|
<span style="font-weight: 600; font-size: 16px">
|
||||||
</h3>
|
{{ slotProps.name }}
|
||||||
|
</span>
|
||||||
|
</Ellipsis>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<div class="content-des-title">
|
<div class="content-des-title">
|
||||||
关联场景联动
|
关联场景联动
|
||||||
</div>
|
</div>
|
||||||
<div class="rule-desc">
|
<Ellipsis
|
||||||
{{ (slotProps?.scene || []).map((item: any) => item?.name).join(',') || '' }}
|
><div>
|
||||||
</div>
|
{{ (slotProps?.scene || []).map((item: any) => item?.name).join(',') || '' }}
|
||||||
|
</div></Ellipsis
|
||||||
|
>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<div class="content-des-title">
|
<div class="content-des-title">
|
||||||
告警级别
|
告警级别
|
||||||
</div>
|
</div>
|
||||||
<div class="rule-desc">
|
<div>
|
||||||
{{ (Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title ||
|
{{ (Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title ||
|
||||||
slotProps.level }}
|
slotProps.level }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -200,50 +205,124 @@ import {
|
||||||
_disable,
|
_disable,
|
||||||
remove,
|
remove,
|
||||||
_execute,
|
_execute,
|
||||||
|
getScene,
|
||||||
} from '@/api/rule-engine/configuration';
|
} from '@/api/rule-engine/configuration';
|
||||||
import { queryLevel } from '@/api/rule-engine/config';
|
import { queryLevel } from '@/api/rule-engine/config';
|
||||||
import { Store } from 'jetlinks-store';
|
import { Store } from 'jetlinks-store';
|
||||||
import type { ActionsType } from '@/components/Table/index.vue';
|
import type { ActionsType } from '@/components/Table/index.vue';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { getImage } from '@/utils/comm';
|
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>>({});
|
const params = ref<Record<string, any>>({});
|
||||||
let isAdd = ref<number>(0);
|
let isAdd = ref<number>(0);
|
||||||
let title = ref<string>('');
|
let title = ref<string>('');
|
||||||
const tableRef = ref<Record<string, any>>({});
|
const tableRef = ref<Record<string, any>>({});
|
||||||
|
const menuStory = useMenuStore();
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '类型',
|
title: '类型',
|
||||||
dataIndex: 'targetType',
|
dataIndex: 'targetType',
|
||||||
key: 'targetType',
|
key: 'targetType',
|
||||||
scopedSlots: true,
|
scopedSlots: true,
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: '产品',
|
||||||
|
value: 'product',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '设备',
|
||||||
|
value: 'device',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '组织',
|
||||||
|
value: 'org',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '其他',
|
||||||
|
value: 'other',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '告警级别',
|
title: '告警级别',
|
||||||
dataIndex: 'level',
|
dataIndex: 'level',
|
||||||
key: 'level',
|
key: 'level',
|
||||||
scopedSlots: true,
|
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: '关联场景联动',
|
title: '关联场景联动',
|
||||||
dataIndex: 'sceneId',
|
dataIndex: 'sceneId',
|
||||||
wdith: 250,
|
wdith: 250,
|
||||||
scopedSlots: true,
|
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: '状态',
|
title: '状态',
|
||||||
dataIndex: 'state',
|
dataIndex: 'state',
|
||||||
key: 'state',
|
key: 'state',
|
||||||
scopedSlots: true,
|
scopedSlots: true,
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: '正常',
|
||||||
|
value: 'enabled',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '禁用',
|
||||||
|
value: 'disabled',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '说明',
|
title: '说明',
|
||||||
dataIndex: 'description',
|
dataIndex: 'description',
|
||||||
key: 'description',
|
key: 'description',
|
||||||
|
search:{
|
||||||
|
type:'string',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
|
@ -259,44 +338,6 @@ const map = {
|
||||||
org: '组织',
|
org: '组织',
|
||||||
other: '其他',
|
other: '其他',
|
||||||
};
|
};
|
||||||
const query = {
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
title: '名称',
|
|
||||||
dataIndex: 'name',
|
|
||||||
key: 'name',
|
|
||||||
search: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '状态',
|
|
||||||
dataIndex: 'state',
|
|
||||||
key: 'state',
|
|
||||||
search: {
|
|
||||||
type: 'select',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: '正常',
|
|
||||||
value: 'enabled',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '禁用',
|
|
||||||
value: 'disabled',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '说明',
|
|
||||||
key: 'description',
|
|
||||||
dataIndex: 'description',
|
|
||||||
search: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const handleSearch = (e: any) => {
|
const handleSearch = (e: any) => {
|
||||||
params.value = e;
|
params.value = e;
|
||||||
};
|
};
|
||||||
|
@ -355,9 +396,7 @@ const getActions = (
|
||||||
|
|
||||||
icon: 'EditOutlined',
|
icon: 'EditOutlined',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
title.value = '编辑';
|
menuStory.jumpPage('rule-engine/Alarm/Configuration/Save',{},{id:data.id});
|
||||||
isAdd.value = 2;
|
|
||||||
nextTick(() => {});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -421,14 +460,12 @@ const getActions = (
|
||||||
return actions.filter((i: ActionsType) => i.key !== 'view');
|
return actions.filter((i: ActionsType) => i.key !== 'view');
|
||||||
return actions;
|
return actions;
|
||||||
};
|
};
|
||||||
|
const add = () => {
|
||||||
|
menuStory.jumpPage('rule-engine/Alarm/Configuration/Save');
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.content-des-title {
|
.content-des-title {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
.rule-desc {
|
|
||||||
white-space: nowrap; /*强制在同一行内显示所有文本,直到文本结束或者遭遇br标签对象才换行。*/
|
|
||||||
overflow: hidden; /*超出部分隐藏*/
|
|
||||||
text-overflow: ellipsis; /*隐藏部分以省略号代替*/
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<template>
|
||||||
|
<div></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
</style>
|
|
@ -1,7 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<page-container>
|
<page-container>
|
||||||
<div>
|
<div>
|
||||||
<Search :columns="query.columns" target="device-instance" @search="handleSearch"></Search>
|
<Search
|
||||||
|
:columns="query.columns"
|
||||||
|
target="device-instance"
|
||||||
|
@search="handleSearch"
|
||||||
|
></Search>
|
||||||
<JTable
|
<JTable
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:request="queryList"
|
:request="queryList"
|
||||||
|
@ -14,7 +18,7 @@
|
||||||
<template #headerTitle>
|
<template #headerTitle>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button type="primary" @click="add"
|
<a-button type="primary" @click="add"
|
||||||
><plus-outlined/>新增</a-button
|
><plus-outlined />新增</a-button
|
||||||
>
|
>
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
|
@ -36,14 +40,18 @@
|
||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<h3 style="font-weight: 600">
|
<Ellipsis>
|
||||||
{{ slotProps.name }}
|
<span style="font-weight: 600; font-size: 16px">
|
||||||
</h3>
|
{{ slotProps.name }}
|
||||||
|
</span>
|
||||||
|
</Ellipsis>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<div class="rule-desc">
|
<Ellipsis>
|
||||||
{{ slotProps.description }}
|
<div>
|
||||||
</div>
|
{{ slotProps.description }}
|
||||||
|
</div>
|
||||||
|
</Ellipsis>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</template>
|
</template>
|
||||||
|
@ -154,7 +162,12 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import JTable from '@/components/Table';
|
import JTable from '@/components/Table';
|
||||||
import type { InstanceItem } from './typings';
|
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 type { ActionsType } from '@/components/Table/index.vue';
|
||||||
import { getImage } from '@/utils/comm';
|
import { getImage } from '@/utils/comm';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
@ -266,7 +279,10 @@ const getActions = (
|
||||||
tooltip: {
|
tooltip: {
|
||||||
title: data.state?.value !== 'disable' ? '禁用' : '启用',
|
title: data.state?.value !== 'disable' ? '禁用' : '启用',
|
||||||
},
|
},
|
||||||
icon: data.state?.value !== 'disable' ? 'StopOutlined' : 'CheckCircleOutlined',
|
icon:
|
||||||
|
data.state?.value !== 'disable'
|
||||||
|
? 'StopOutlined'
|
||||||
|
: 'CheckCircleOutlined',
|
||||||
popConfirm: {
|
popConfirm: {
|
||||||
title: `确认${data.state !== 'disable' ? '禁用' : '启用'}?`,
|
title: `确认${data.state !== 'disable' ? '禁用' : '启用'}?`,
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
|
@ -332,9 +348,4 @@ const handleSearch = (e: any) => {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.rule-desc {
|
|
||||||
white-space: nowrap; /*强制在同一行内显示所有文本,直到文本结束或者遭遇br标签对象才换行。*/
|
|
||||||
overflow: hidden; /*超出部分隐藏*/
|
|
||||||
text-overflow: ellipsis; /*隐藏部分以省略号代替*/
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
|
@ -6,7 +6,7 @@
|
||||||
@click='save'
|
@click='save'
|
||||||
@cancel='cancel'
|
@cancel='cancel'
|
||||||
>
|
>
|
||||||
<a-steps :current='addModel.stepNumber'>
|
<a-steps :current='addModel.stepNumber' @change='stepChange'>
|
||||||
<a-step>
|
<a-step>
|
||||||
<template #title>选择产品</template>
|
<template #title>选择产品</template>
|
||||||
</a-step>
|
</a-step>
|
||||||
|
@ -17,19 +17,28 @@
|
||||||
<template #title>触发类型</template>
|
<template #title>触发类型</template>
|
||||||
</a-step>
|
</a-step>
|
||||||
</a-steps>
|
</a-steps>
|
||||||
|
<a-divider style='margin-bottom: 0px' />
|
||||||
<div class='steps-content'>
|
<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>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class='steps-action'>
|
<div class='steps-action'>
|
||||||
<template>
|
<a-button v-if='addModel.stepNumber === 0' @click='cancel'>取消</a-button>
|
||||||
<a-button v-if='addModel.stepNumber === 0' @click='cancel'>取消</a-button>
|
<a-button v-else @click='prev'>上一步</a-button>
|
||||||
<a-button v-else>上一步</a-button>
|
<a-button type='primary' v-if='addModel.stepNumber < 2' @click='saveClick'>下一步</a-button>
|
||||||
</template>
|
<a-button type='primary' v-else @click='saveClick'>确定</a-button>
|
||||||
<template>
|
|
||||||
<a-button type='primary' v-if='addModel.stepNumber < 2'>下一步</a-button>
|
|
||||||
<a-button type='primary' v-else>确定</a-button>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
@ -37,10 +46,12 @@
|
||||||
|
|
||||||
<script setup lang='ts' name='AddModel'>
|
<script setup lang='ts' name='AddModel'>
|
||||||
import type { PropType } from 'vue'
|
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 { onlyMessage } from '@/utils/comm'
|
||||||
import { detail as deviceDetail } from '@/api/device/instance'
|
import { detail as deviceDetail } from '@/api/device/instance'
|
||||||
import Product from './Product.vue'
|
import Product from './Product.vue'
|
||||||
|
import DeviceSelect from './DeviceSelect.vue'
|
||||||
|
import Type from './Type.vue'
|
||||||
|
|
||||||
type Emit = {
|
type Emit = {
|
||||||
(e: 'cancel'): void
|
(e: 'cancel'): void
|
||||||
|
@ -54,11 +65,7 @@ interface AddModelType extends Omit<TriggerDevice, 'selectorValues'> {
|
||||||
orgId: Array<{ label: string, value: string }>
|
orgId: Array<{ label: string, value: string }>
|
||||||
productDetail: any
|
productDetail: any
|
||||||
selectorValues: Array<{ label: string, value: string }>
|
selectorValues: Array<{ label: string, value: string }>
|
||||||
metadata: {
|
metadata: metadataType
|
||||||
properties?: any[]
|
|
||||||
functions?: any[]
|
|
||||||
events?: any[]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits<Emit>()
|
const emit = defineEmits<Emit>()
|
||||||
|
@ -97,39 +104,56 @@ const handleOptions = () => {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const prev = () => {
|
||||||
|
addModel.stepNumber = addModel.stepNumber - 1
|
||||||
|
}
|
||||||
|
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
emit("cancel")
|
emit("cancel")
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMetadata = (metadata: string) => {
|
const handleMetadata = (metadata?: string) => {
|
||||||
try {
|
try {
|
||||||
addModel.metadata = JSON.parse(metadata)
|
addModel.metadata = JSON.parse(metadata || "{}")
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('handleMetadata: ' + e)
|
console.warn('handleMetadata: ' + e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const save = async () => {
|
const save = async (step?: number) => {
|
||||||
if (addModel.stepNumber === 0) {
|
let _step = step !== undefined ? step : addModel.stepNumber
|
||||||
|
if (_step === 0) {
|
||||||
addModel.productId ? addModel.stepNumber = 1 : onlyMessage('请选择产品', 'error')
|
addModel.productId ? addModel.stepNumber = 1 : onlyMessage('请选择产品', 'error')
|
||||||
} else if (addModel.stepNumber === 1) {
|
} else if (_step === 1) {
|
||||||
const isFixed = addModel.selector === 'fixed' // 是否选择方式为设备
|
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')
|
return onlyMessage(isFixed ? '请选择设备' : '请选择部门', 'error')
|
||||||
}
|
}
|
||||||
// 选择方式为设备且仅选中一个设备时,物模型取该设备
|
// 选择方式为设备且仅选中一个设备时,物模型取该设备
|
||||||
if (isFixed && addModel.selectorValues?.length === 1) {
|
if (isFixed && addModel.selectorValues?.length === 1) {
|
||||||
const resp = await deviceDetail(addModel.selectorValues[0].value)
|
const resp = await deviceDetail(addModel.selectorValues[0].value)
|
||||||
addModel.metadata
|
handleMetadata(resp.result.metadata)
|
||||||
} else {
|
} else {
|
||||||
|
handleMetadata(addModel.productDetail?.metadata)
|
||||||
}
|
}
|
||||||
//
|
addModel.stepNumber = 2
|
||||||
|
} else {
|
||||||
|
|
||||||
}
|
}
|
||||||
// handleOptions()
|
// handleOptions()
|
||||||
// emit('update:value', {})
|
// emit('update:value', {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const saveClick = () => save()
|
||||||
|
|
||||||
|
const stepChange = (step: number) => {
|
||||||
|
if (step !== 0) {
|
||||||
|
save(step - 1)
|
||||||
|
} else {
|
||||||
|
addModel.stepNumber = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<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'
|
type='simple'
|
||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
class='search'
|
class='search'
|
||||||
|
target="scene-triggrt-device-device"
|
||||||
/>
|
/>
|
||||||
|
<a-divider style='margin: 0' />
|
||||||
<j-table
|
<j-table
|
||||||
:columns='columns'
|
|
||||||
ref='actionRef'
|
ref='actionRef'
|
||||||
|
model='CARD'
|
||||||
|
:columns='columns'
|
||||||
|
:params='params'
|
||||||
:request='productQuery'
|
:request='productQuery'
|
||||||
:gridColumn='2'
|
:gridColumn='2'
|
||||||
model='CARD'
|
:bodyStyle='{
|
||||||
|
paddingRight: 0,
|
||||||
|
paddingLeft: 0
|
||||||
|
}'
|
||||||
>
|
>
|
||||||
<template #card="slotProps">
|
<template #card="slotProps">
|
||||||
<CardBox
|
<CardBox
|
||||||
:value='slotProps'
|
:value='slotProps'
|
||||||
:active="selectedRowKeys.includes(slotProps.id)"
|
:active="rowKey === slotProps.id"
|
||||||
:status="slotProps.state"
|
:status="slotProps.state"
|
||||||
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
||||||
:statusNames="{ 1: 'success', 0: 'error', }"
|
:statusNames="{ 1: 'success', 0: 'error', }"
|
||||||
|
@ -23,13 +30,17 @@
|
||||||
>
|
>
|
||||||
<template #img>
|
<template #img>
|
||||||
<slot name="img">
|
<slot name="img">
|
||||||
<img :src="getImage('/device-product.png')" />
|
<img width='88' height='88' :src="slotProps.photoUrl || getImage('/device-product.png')" />
|
||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<h3 style="font-weight: 600" >
|
<div style='width: calc(100% - 100px)'>
|
||||||
{{ slotProps.name }}
|
<Ellipsis>
|
||||||
</h3>
|
<span style="font-size: 16px;font-weight: 600" >
|
||||||
|
{{ slotProps.name }}
|
||||||
|
</span>
|
||||||
|
</Ellipsis>
|
||||||
|
</div>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<div class="card-item-content-text">
|
<div class="card-item-content-text">
|
||||||
|
@ -51,16 +62,25 @@ import { getTreeData_api } from '@/api/system/department'
|
||||||
import { isNoCommunity } from '@/utils/utils'
|
import { isNoCommunity } from '@/utils/utils'
|
||||||
import { getImage } from '@/utils/comm'
|
import { getImage } from '@/utils/comm'
|
||||||
|
|
||||||
|
type Emit = {
|
||||||
|
(e: 'update:rowKey', data: string): void
|
||||||
|
(e: 'update:detail', data: string): void
|
||||||
|
}
|
||||||
|
|
||||||
const actionRef = ref()
|
const actionRef = ref()
|
||||||
const params = ref({})
|
const params = ref({})
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
rowKey: {
|
rowKey: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedRowKeys = ref(props.rowKey)
|
const emit = defineEmits<Emit>()
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
|
@ -69,12 +89,19 @@ const columns = [
|
||||||
width: 300,
|
width: 300,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
width: 200,
|
width: 200,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
first: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '网关类型',
|
title: '网关类型',
|
||||||
|
@ -199,7 +226,6 @@ const columns = [
|
||||||
|
|
||||||
const handleSearch = (p: any) => {
|
const handleSearch = (p: any) => {
|
||||||
params.value = p
|
params.value = p
|
||||||
actionRef.value.required()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const productQuery = (p: any) => {
|
const productQuery = (p: any) => {
|
||||||
|
@ -217,12 +243,8 @@ const productQuery = (p: any) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClick = (detail: any) => {
|
const handleClick = (detail: any) => {
|
||||||
const _selected = new Set(selectedRowKeys.value)
|
emit('update:rowKey', detail.id)
|
||||||
if (_selected.has(detail.id)) {
|
emit('update:detail', detail)
|
||||||
_selected.delete(detail.id)
|
|
||||||
} else {
|
|
||||||
_selected.add(detail.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -230,5 +252,7 @@ const handleClick = (detail: any) => {
|
||||||
<style scoped lang='less'>
|
<style scoped lang='less'>
|
||||||
.search {
|
.search {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
padding-right: 0px;
|
||||||
|
padding-left: 0px;
|
||||||
}
|
}
|
||||||
</style>
|
</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'
|
import Title from '../components/Title.vue'
|
||||||
|
|
||||||
const sceneStore = useSceneStore()
|
const sceneStore = useSceneStore()
|
||||||
const { data } = storeToRefs(sceneStore)
|
const { data } = storeToRefs<any>(sceneStore)
|
||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
|
|
||||||
const rules = [{
|
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;
|
text: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type optionItem = {
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
type Action = {
|
type Action = {
|
||||||
executor: string;
|
executor: string;
|
||||||
configuration: Record<string, unknown>;
|
configuration: Record<string, unknown>;
|
||||||
|
@ -311,3 +316,9 @@ export interface FormModelType {
|
||||||
options?: Record<string, any>;
|
options?: Record<string, any>;
|
||||||
description?: string;
|
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>
|
<template>
|
||||||
<div class="form-label-container">
|
<div class="form-label-container">
|
||||||
<span class="text">{{ props.text }}</span>
|
<span class="text">{{ props.text }}</span>
|
||||||
<span class="required">*</span>
|
<span class="required" v-show="props.required">*</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template #title>{{ props.tooltip }}</template>
|
<template #title>{{ props.tooltip }}</template>
|
||||||
<AIcon type="QuestionCircleOutlined" style="color: #00000073;cursor: inherit;" />
|
<AIcon type="QuestionCircleOutlined" class="icon" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -24,11 +24,15 @@ const props = defineProps<{
|
||||||
|
|
||||||
.required {
|
.required {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 4px;
|
|
||||||
color: #ff4d4f;
|
color: #ff4d4f;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: SimSun, sans-serif;
|
font-family: SimSun, sans-serif;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
.icon {
|
||||||
|
color: #00000073;
|
||||||
|
cursor: inherit;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</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-card class="save-container">
|
||||||
<a-row :gutter="24">
|
<a-row :gutter="24">
|
||||||
<a-col :span="14">
|
<a-col :span="14">
|
||||||
<a-form
|
<EditForm @change-apply-type="chengeType" />
|
||||||
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>
|
|
||||||
</a-col>
|
</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-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</page-container>
|
</page-container>
|
||||||
|
@ -355,151 +13,11 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Does from './components/Does.vue';
|
import Does from './components/Does.vue';
|
||||||
import FormLabel from './components/FormLabel.vue';
|
import EditForm from './components/EditForm.vue';
|
||||||
import { getImage } from '@/utils/comm';
|
import type { applyType } from './typing';
|
||||||
import type { applyType, formType } from './typing';
|
|
||||||
const routeQuery = useRoute().query;
|
|
||||||
|
|
||||||
const initForm: formType = {
|
const rightType = ref<applyType>('internal-standalone');
|
||||||
name: '',
|
const chengeType = (newType: applyType) => {
|
||||||
provider: 'internal-standalone',
|
rightType.value = newType;
|
||||||
integrationModes: [],
|
|
||||||
config: '',
|
|
||||||
description: '',
|
|
||||||
page: {
|
|
||||||
baseUrl: '',
|
|
||||||
routeType: 'hash',
|
|
||||||
},
|
|
||||||
apiClient: {
|
|
||||||
baseUrl: '',
|
|
||||||
authConfig: {
|
|
||||||
type: '',
|
|
||||||
oauth2: {
|
|
||||||
authorizationUrl: '',
|
|
||||||
tokenUrl: '',
|
|
||||||
redirectUri: '',
|
|
||||||
clientId: '',
|
|
||||||
clientSecret: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
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>
|
</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'
|
| 'internal-integrated'
|
||||||
| 'dingtalk-ent-app'
|
| 'dingtalk-ent-app'
|
||||||
| 'third-party'
|
| 'third-party'
|
||||||
|
export type dictType = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
children?: dictType
|
||||||
|
}[];
|
||||||
|
|
||||||
|
export type optionsType = {
|
||||||
|
label: string,
|
||||||
|
value: string;
|
||||||
|
disabled?: boolean
|
||||||
|
}[]
|
||||||
export type formType = {
|
export type formType = {
|
||||||
|
id?:string,
|
||||||
name: string;
|
name: string;
|
||||||
provider: applyType;
|
provider: applyType;
|
||||||
integrationModes: string[];
|
integrationModes: string[];
|
||||||
config: string;
|
config: string;
|
||||||
description: string;
|
description: string;
|
||||||
page: {
|
page: { // 页面集成
|
||||||
baseUrl:string,
|
|
||||||
routeType:'hash' | 'history'
|
|
||||||
},
|
|
||||||
apiClient: {
|
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
authConfig: {
|
routeType: 'hash' | 'history',
|
||||||
type:string,
|
parameters: optionsType
|
||||||
oauth2 :{
|
},
|
||||||
authorizationUrl:string,
|
apiClient: { // API客户端
|
||||||
tokenUrl: string,
|
baseUrl: string, // 接口地址
|
||||||
redirectUri:string,
|
headers: optionsType, // 请求头
|
||||||
clientId:string,
|
parameters: optionsType, // 请求参数
|
||||||
clientSecret:string
|
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