diff --git a/package.json b/package.json index f7040682..942a5662 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,11 @@ "dependencies": { "@vitejs/plugin-vue-jsx": "^3.0.0", "@vuemap/vue-amap": "^1.1.20", + "@vueuse/core": "^9.10.0", "ant-design-vue": "^3.2.15", "axios": "^1.2.1", "echarts": "^5.4.1", + "event-source-polyfill": "^1.0.31", "jetlinks-store": "^0.0.3", "js-cookie": "^3.0.1", "less": "^4.1.3", diff --git a/public/images/device-type-3.png b/public/images/device-type-3.png deleted file mode 100644 index 3561237e..00000000 Binary files a/public/images/device-type-3.png and /dev/null differ diff --git a/public/images/device-type-3-big.png b/public/images/device/instance/device-card.png similarity index 100% rename from public/images/device-type-3-big.png rename to public/images/device/instance/device-card.png diff --git a/src/api/comm.ts b/src/api/comm.ts new file mode 100644 index 00000000..71d6aa82 --- /dev/null +++ b/src/api/comm.ts @@ -0,0 +1,25 @@ +import { BASE_API_PATH } from "@/utils/variable"; +import server from '@/utils/request' +import { SearchHistoryList } from 'components/Search/types' + +export const FILE_UPLOAD = `${BASE_API_PATH}/file/static`; + +/** + * 保存查询记录 + * @param data + * @param target + */ +export const saveSearchHistory = (data: any, target:string) => server.post(`/user/settings/${target}`, data) + +/** + * 获取查询记录 + * @param target + */ +export const getSearchHistory = (target:string) => server.get(`/user/settings/${target}`) + +/** + * 删除指定查询记录 + * @param id + * @param target + */ +export const deleteSearchHistory = (target:string, id:string) => server.remove(`/user/settings/${target}/${id}`) diff --git a/src/api/device/instance.ts b/src/api/device/instance.ts index 8c3c964f..9f051caf 100644 --- a/src/api/device/instance.ts +++ b/src/api/device/instance.ts @@ -1,4 +1,5 @@ import server from '@/utils/request' +import { BASE_API_PATH } from '@/utils/variable' import { DeviceInstance } from '@/views/device/instance/typings' /** @@ -21,4 +22,80 @@ export const saveMetadata = (id: string, data: string) => server.put(`/device/in * @param id 设备ID * @returns 设备详情 */ -export const detail = (id: string) => server.get(`/device-instance/${id}/detail`) \ No newline at end of file +export const detail = (id: string) => server.get(`/device-instance/${id}/detail`) + +/** + * 查询数据 + * @param data 分页搜索数据 + * @returns + */ +export const query = (data?: Record) => server.post('/device-instance/_query', data) + +/** + * 删除设备 + * @param id 设备ID + * @returns + */ +export const _delete = (id: string) => server.remove(`/device-instance/${id}`) + +/** + * 启用设备 + * @param id 设备ID + * @param data + * @returns + */ +export const _deploy = (id: string) => server.post(`/device-instance/${id}/deploy`) + +/** + * 禁用设备 + * @param id 设备ID + * @param data + * @returns + */ +export const _undeploy = (id: string) => server.post(`/device-instance/${id}/undeploy`) + +/** + * 批量激活设备 + * @param data 设备id数组 + * @returns + */ +export const batchDeployDevice = (data: string[]) => server.put(`/device-instance/batch/_deploy`, data) + +/** + * 批量注销设备 + * @param data 设备id数组 + * @returns + */ +export const batchUndeployDevice = (data: string[]) => server.put(`/device-instance/batch/_unDeploy`, data) + +/** + * 批量删除 + * @param data 设备id数组 + * @returns + */ +export const batchDeleteDevice = (data: string[]) => server.put(`/device-instance/batch/_delete`, data) + +/** + * 下载设备模板 + * @param productId 产品id + * @param type 文件类型 + * @returns + */ + export const deviceTemplateDownload = (productId: string, type: string) => `${BASE_API_PATH}/device-instance/${productId}/template.${type}` + + /** + * 设备导入 + * @param productId 产品id + * @param type 文件类型 + * @returns + */ + export const deviceImport = (productId: string, fileUrl: string, autoDeploy: boolean) => `${BASE_API_PATH}/device-instance/${productId}/import?fileUrl=${fileUrl}&autoDeploy=${autoDeploy}&:X_Access_Token=${LocalStore.get(TOKEN_KEY)}` + + /** + * 设备导出 + * @param productId 产品id + * @param type 文件类型 + * @returns + */ + export const deviceExport = (productId: string, type: string) => `${BASE_API_PATH}/device-instance${!!productId ? '/' + productId : ''}/export.${type}` + diff --git a/src/api/device/product.ts b/src/api/device/product.ts index 68f8b787..6f318dcf 100644 --- a/src/api/device/product.ts +++ b/src/api/device/product.ts @@ -36,4 +36,10 @@ export const getCodecs = () => server.get<{id: string, name: string}>('/device/p * @param id 产品ID * @returns */ -export const detail = (id: string) => server.get(`/device-product/${id}`) \ No newline at end of file +export const detail = (id: string) => server.get(`/device-product/${id}`) + +/** + * 产品分类 + * @param data + */ +export const category = (data: any) => server.post('/device/category/_tree', data) \ No newline at end of file diff --git a/src/api/home.js b/src/api/home.js index ee609220..ea3aa339 100644 --- a/src/api/home.js +++ b/src/api/home.js @@ -5,4 +5,4 @@ export const getDeviceCount_api = () => server.get(`/device/instance/_count`); // 产品数量 export const getProductCount_api = (data) => server.post(`/device-product/_count`, data); // 查询产品列表 -export const getProductList_api = (data) => server.get(`/device/product/_query/no-paging?paging=false`, data); \ No newline at end of file +export const getProductList_api = (data) => server.get(`/device/product/_query/no-paging?paging=false`, data); diff --git a/src/api/link/accessConfig.js b/src/api/link/accessConfig.js deleted file mode 100644 index 0b972bb5..00000000 --- a/src/api/link/accessConfig.js +++ /dev/null @@ -1,33 +0,0 @@ -import server from '@/utils/request'; - -export const getProviders = () => server.get(`/gateway/device/providers`); - -export const detail = (id) => server.get(`/gateway/device/${id}`); - -export const getNetworkList = (networkType, data, params) => - server.get( - `/network/config/${networkType}/_alive?include=${params.include}`, - data, - ); - -export const getProtocolList = (transport, params) => - server.get(`/protocol/supports/${transport ? transport : ''}`, params); - -export const getConfigView = (id, transport) => - server.get(`/protocol/${id}/transport/${transport}`); - -export const getChildConfigView = (id) => - server.get(`/protocol/${id}/transports`); - -export const save = (data) => server.post(`/gateway/device`, data); - -export const update = (data) => server.patch(`/gateway/device`, data); - -export const list = (data) => - server.post(`/gateway/device/detail/_query`, data); - -export const undeploy = (id) => server.post(`/gateway/device/${id}/_shutdown`); - -export const deploy = (id) => server.post(`/gateway/device/${id}/_startup`); - -export const del = (id) => server.remove(`/gateway/device/${id}`); diff --git a/src/api/link/accessConfig.ts b/src/api/link/accessConfig.ts new file mode 100644 index 00000000..0aaa1986 --- /dev/null +++ b/src/api/link/accessConfig.ts @@ -0,0 +1,45 @@ +import server from '@/utils/request'; + +export const getProviders = () => server.get(`/gateway/device/providers`); + +export const detail = (id: string) => server.get(`/gateway/device/${id}`); + +export const getNetworkList = ( + networkType: string, + include: string, + data: Object, +) => + server.get( + `/network/config/${networkType}/_alive?include=${include}`, + data, + ); + +export const getProtocolList = (transport: string, params: Object) => + server.get(`/protocol/supports/${transport ? transport : ''}`, params); + +export const getConfigView = (id: string, transport: string) => + server.get(`/protocol/${id}/transport/${transport}`); + +export const getChildConfigView = (id: string) => + server.get(`/protocol/${id}/transports`); + +export const save = (data: Object) => server.post(`/gateway/device`, data); + +export const update = (data: Object) => server.patch(`/gateway/device`, data); + +export const list = (data: Object) => + server.post(`/gateway/device/detail/_query`, data); + +export const undeploy = (id: string) => + server.post(`/gateway/device/${id}/_shutdown`); + +export const deploy = (id: string) => + server.post(`/gateway/device/${id}/_startup`); + +export const del = (id: string) => server.remove(`/gateway/device/${id}`); + +export const getResourcesCurrent = () => + server.get(`/network/resources/alive/_current`); + +export const getClusters = () => + server.get(`network/resources/clusters`); diff --git a/src/api/link/certificate.js b/src/api/link/certificate.js deleted file mode 100644 index 24fe25f9..00000000 --- a/src/api/link/certificate.js +++ /dev/null @@ -1,3 +0,0 @@ -import server from '@/utils/request' - -export const save = (data) => server.post(`/network/certificate`, data) \ No newline at end of file diff --git a/src/api/link/certificate.ts b/src/api/link/certificate.ts new file mode 100644 index 00000000..9ed5f5e1 --- /dev/null +++ b/src/api/link/certificate.ts @@ -0,0 +1,7 @@ +import server from '@/utils/request'; +import { BASE_API_PATH } from '@/utils/variable'; + +export const NETWORK_CERTIFICATE_UPLOAD = `${BASE_API_PATH}/network/certificate/upload`; + + +export const save = (data: object) => server.post(`/network/certificate`, data); diff --git a/src/api/login.js b/src/api/login.js index edf859f8..461d7628 100644 --- a/src/api/login.js +++ b/src/api/login.js @@ -12,4 +12,6 @@ export const postInitSet = (data) => server.post(`/user/settings/init`, data) export const systemVersion = () => server.get(`/system/version`) -export const bindInfo = () => server.get(`/application/sso/_all`) \ No newline at end of file +export const bindInfo = () => server.get(`/application/sso/_all`) + +export const settingDetail = (scopes) => server.get(`/system/config/${scopes}`) \ No newline at end of file diff --git a/src/api/northbound/alicloud.ts b/src/api/northbound/alicloud.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/api/northbound/dueros.ts b/src/api/northbound/dueros.ts new file mode 100644 index 00000000..e3cb60cc --- /dev/null +++ b/src/api/northbound/dueros.ts @@ -0,0 +1,8 @@ +import server from '@/utils/request' + +/** + * 查询数据 + * @param data 分页搜索数据 + * @returns + */ +export const query = (data: Record) => server.post('/dueros/product/_query', data) \ No newline at end of file diff --git a/src/api/notice/config.ts b/src/api/notice/config.ts new file mode 100644 index 00000000..9ed55cf8 --- /dev/null +++ b/src/api/notice/config.ts @@ -0,0 +1,12 @@ +import { patch, post, get } from '@/utils/request' + +export default { + // 列表 + list: (data: any) => post(`/notifier/config/_query`, data), + // 详情 + detail: (id: string): any => get(`/notifier/config/${id}`), + // 新增 + save: (data: any) => post(`/notifier/config`, data), + // 修改 + update: (data: any) => patch(`/notifier/config`, data) +} \ No newline at end of file diff --git a/src/api/notice/template.ts b/src/api/notice/template.ts new file mode 100644 index 00000000..b49dbd8b --- /dev/null +++ b/src/api/notice/template.ts @@ -0,0 +1,12 @@ +import { patch, post, get } from '@/utils/request' + +export default { + // 列表 + list: (data: any) => post(`/notifier/template/_query`, data), + // 详情 + detail: (id: string): any => get(`/notifier/template/${id}`), + // 新增 + save: (data: any) => post(`/notifier/template`, data), + // 修改 + update: (data: any) => patch(`/notifier/template`, data) +} \ No newline at end of file diff --git a/src/api/system/basis.ts b/src/api/system/basis.ts new file mode 100644 index 00000000..711a0bcd --- /dev/null +++ b/src/api/system/basis.ts @@ -0,0 +1,6 @@ +import server from '@/utils/request'; + +// 保存 +export const save_api = (data: any) => server.post(`/system/config/scope/_save`, data) +// 获取详情 +export const getDetails_api = (data: any) => server.post(`/system/config/scopes`, data) diff --git a/src/api/system/role.ts b/src/api/system/role.ts new file mode 100644 index 00000000..21eb73fd --- /dev/null +++ b/src/api/system/role.ts @@ -0,0 +1,16 @@ +import server from '@/utils/request'; + +// 获取角色列表 +export const getRoleList_api = (data: any): Promise => server.post(`/role/_query/`, data); +// 删除角色 +export const delRole_api = (id: string): Promise => server.remove(`/role/${id}`); +// 保存角色 +export const saveRole_api = (data: any): Promise => server.post(`/role`, data); +// 获取角色对应的权限树 +export const getPrimissTree_api = (id: string): Promise => server.get(`/menu/role/${id}/_grant/tree`); + + +// 获取用户列表 +export const getUserByRole_api = (data: any): Promise => server.post(`/user/_query/`, data); +// 将用户与该角色进行绑定 +export const bindUser_api = (roleId:string, data: string[]): Promise => server.post(`/role/${roleId}/users/_bind`, data); \ No newline at end of file diff --git a/src/components/AIcon/index.ts b/src/components/AIcon/index.ts deleted file mode 100644 index 39b2aee7..00000000 --- a/src/components/AIcon/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createFromIconfontCN } from '@ant-design/icons-vue'; - -const AliIcon = createFromIconfontCN({ - scriptUrl: '/icons/iconfont.js', // 在 iconfont.cn 上生成 -}); - -export default AliIcon \ No newline at end of file diff --git a/src/components/AIcon/index.tsx b/src/components/AIcon/index.tsx new file mode 100644 index 00000000..02e6d8a2 --- /dev/null +++ b/src/components/AIcon/index.tsx @@ -0,0 +1,37 @@ +import { createFromIconfontCN } from '@ant-design/icons-vue'; +import * as $Icon from '@ant-design/icons-vue'; +import { createVNode } from 'vue'; + +const AliIcon = createFromIconfontCN({ + scriptUrl: '/icons/iconfont.js', // 在 iconfont.cn 上生成 +}); + +const AntdIcon = (props: {type: string}) => { + const {type} = props; + let antIcon: {[key: string]: any} = $Icon + return createVNode(antIcon[type]) +} + +const iconKeys = [ + 'EyeOutlined', + 'EditOutlined', + 'PlusOutlined', + 'DeleteOutlined', + 'CheckCircleOutlined', + 'StopOutlined', + 'CheckOutlined', + 'CloseOutlined', + 'DownOutlined', + 'ImportOutlined', + 'ExportOutlined', + 'SyncOutlined', + 'ExclamationCircleOutlined', + 'UploadOutlined' +] + +const Icon = (props: {type: string}) => { + if(iconKeys.includes(props.type)) return + return +} + +export default Icon \ No newline at end of file diff --git a/src/components/BadgeStatus/index.vue b/src/components/BadgeStatus/index.vue index 47870618..067ee58b 100644 --- a/src/components/BadgeStatus/index.vue +++ b/src/components/BadgeStatus/index.vue @@ -6,8 +6,7 @@ \ No newline at end of file diff --git a/src/components/Form/FormBuilder.vue b/src/components/Form/FormBuilder.vue index afcbfa2d..e93696d7 100644 --- a/src/components/Form/FormBuilder.vue +++ b/src/components/Form/FormBuilder.vue @@ -31,7 +31,7 @@ v-model:value='formData.data[item.name]' :options='item.options' /> - + + avatar +
+ + +
Upload
+
+
+ + + + + \ No newline at end of file diff --git a/src/components/MonacoEditor/index.vue b/src/components/MonacoEditor/index.vue index 9af9c3b1..4ec0f91d 100644 --- a/src/components/MonacoEditor/index.vue +++ b/src/components/MonacoEditor/index.vue @@ -32,6 +32,7 @@ self.MonacoEnvironment = { const props = defineProps({ modelValue: [String, Number], + theme: { type: String, default: 'vs-dark' }, }); const emit = defineEmits(['update:modelValue']); @@ -48,7 +49,7 @@ onMounted(() => { tabSize: 2, automaticLayout: true, scrollBeyondLastLine: false, - theme: 'vs-dark', // 主题色: vs(默认高亮), vs-dark(黑色), hc-black(高亮黑色) + theme: props.theme, // 主题色: vs(默认高亮), vs-dark(黑色), hc-black(高亮黑色) }); instance.onDidChangeModelContent(() => { diff --git a/src/components/NormalUpload/index.vue b/src/components/NormalUpload/index.vue new file mode 100644 index 00000000..6eb48c6d --- /dev/null +++ b/src/components/NormalUpload/index.vue @@ -0,0 +1,116 @@ + + + \ No newline at end of file diff --git a/src/components/Search/History.vue b/src/components/Search/History.vue new file mode 100644 index 00000000..27c58809 --- /dev/null +++ b/src/components/Search/History.vue @@ -0,0 +1,125 @@ + + + + + \ No newline at end of file diff --git a/src/components/Search/Item.vue b/src/components/Search/Item.vue index 6f44c425..aa82e42f 100644 --- a/src/components/Search/Item.vue +++ b/src/components/Search/Item.vue @@ -1,11 +1,12 @@ \ No newline at end of file diff --git a/src/components/Search/search.md b/src/components/Search/search.md new file mode 100644 index 00000000..6248739f --- /dev/null +++ b/src/components/Search/search.md @@ -0,0 +1,107 @@ +# Search组件 + +- 需要结合Table使用 + +## 属性 + +| 名称 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| columns | 查询下拉列表 | JColumnsProps[] | [] | +| type | 查询模式 | 'advanced', 'simple' | 'advanced' | +| target | 查询组件唯一key | String | | +| search | 查询回调事件 | Function | | + +> JColumnsProps[*].search + +| 名称 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| rename | 用来重命名查询字段值 | String | | +| type | 查询值组件类型 | 'select', 'number', 'string', 'treeSelect', 'date', 'time' | | +| options | Select和TreeSelect组件下拉值 | Array, Promise | | +| first | 控制查询字段下拉默认值,默认为name即名称 | Boolean | | +| defaultTermType | 查询条件 | String | | +| handleValue | 处理单个查询value值 | Function | | + +## 基础用法 + +> columns中包含search属性才会出现在查询下拉中 + +```vue + const columns = [ + { + title: '名称', + dataIndex: 'name', + key: 'name', + search: { + type: 'string', + } + } + ] + const search = (params) => { +} + +``` + +> rename的作用在于search抛出params会根据rename修改数据中column的值 + +```vue + const columns = [ + { + title: '名称', + dataIndex: 'name', + key: 'name', + search: { + type: 'string', + rename: 'TestName' + } + } + ] + const search = (params) => { + terms: [ + { + column: 'TestName', + value: '', + termType: 'like' + } + ] + } + +``` + +> defaultTermType的作用在于设置查询条件,相关条件参考util中的termType + +```vue + const columns = [ + { + title: '名称', + dataIndex: 'name', + key: 'name', + search: { + type: 'string', + defaultTermType: 'gt' + } + } + ] + const search = (params) => { + terms: [ + { + column: 'TestName', + value: '', + termType: 'gt' + } + ] + } + +``` diff --git a/src/components/Search/types.d.ts b/src/components/Search/types.d.ts new file mode 100644 index 00000000..7c8c67b6 --- /dev/null +++ b/src/components/Search/types.d.ts @@ -0,0 +1,49 @@ +export interface SearchBaseProps { + rename?: string + type?: 'select' | 'number' | 'string' | 'treeSelect' | 'date' | 'time' + format?: string + options?: any[] | Function + first?: boolean + defaultTermType?: string // 默认 eq + title?: ColumnType.title + sortIndex?: number + handleValue?: (value: SearchItemData) => any +} + +export interface SearchItemProps { + rename?: SearchBaseProps['rename'] + title: string + column: ColumnType.dataIndex +} + +export interface SearchItemData { + column: ColumnType.dataIndex + value: any + termType: string + type?: string +} + +export interface TermsItem { + terms: SearchItemData[] +} + +export interface Terms { + terms: TermsItem[] +} + +export interface SortItem { + name: string + order?: 'desc' | 'asc' + value?: any +} + +export interface SearchHistoryList { + content?: string + name: string + id: string + key: string +} + +export interface SearchProps extends SearchBaseProps, SearchItemProps { + +} diff --git a/src/components/Search/util.ts b/src/components/Search/util.ts index 573b0d7d..7060c14b 100644 --- a/src/components/Search/util.ts +++ b/src/components/Search/util.ts @@ -1,4 +1,17 @@ export const typeOptions = [ { label: '或者', value: 'or' }, { label: '并且', value: 'and' }, -] \ No newline at end of file +] + +export const termType = [ + { label: '=', value: 'eq' }, + { label: '!=', value: 'not' }, + { label: '包含', value: 'like' }, + { label: '不包含', value: 'nlike' }, + { label: '>', value: 'gt' }, + { label: '>=', value: 'gte' }, + { label: '<', value: 'lt' }, + { label: '<=', value: 'lte' }, + { label: '属于', value: 'in' }, + { label: '不属于', value: 'nin' }, +]; \ No newline at end of file diff --git a/src/components/Table/index.tsx b/src/components/Table/index.tsx index 915657c7..92069199 100644 --- a/src/components/Table/index.tsx +++ b/src/components/Table/index.tsx @@ -11,16 +11,21 @@ enum ModelEnum { CARD = 'CARD', } +enum TypeEnum { + TREE = 'TREE', + PAGE = 'PAGE', +} + type RequestData = { code: string; result: { - data: Record[] | undefined; + data?: Record[] | undefined; pageIndex: number; pageSize: number; total: number; }; status: number; -} & Record; +} | Record; export interface ActionsType { key: string; @@ -39,16 +44,10 @@ export interface JColumnProps extends ColumnProps{ } export interface JTableProps extends TableProps{ - request?: (params: Record & { - pageSize: number; - pageIndex: number; - }) => Promise>; + request?: (params?: Record) => Promise>; cardBodyClass?: string; columns: JColumnProps[]; - params?: Record & { - pageSize: number; - pageIndex: number; - }; + params?: Record; model?: keyof typeof ModelEnum | undefined; // 显示table还是card // actions?: ActionsType[]; noPagination?: boolean; @@ -64,6 +63,8 @@ export interface JTableProps extends TableProps{ */ gridColumns?: number[]; alertRender?: boolean; + type?: keyof typeof TypeEnum; + defaultParams?: Record; } const JTable = defineComponent({ @@ -74,6 +75,7 @@ const JTable = defineComponent({ ], emits: [ 'modelChange', // 切换卡片和表格 + 'reload' // 刷新数据 ], props: { request: { @@ -96,10 +98,6 @@ const JTable = defineComponent({ type: [String, undefined], default: undefined }, - // actions: { - // type: Array as PropType, - // default: () => [] - // }, noPagination: { type: Boolean, default: false @@ -127,9 +125,22 @@ const JTable = defineComponent({ alertRender: { type: Boolean, default: true + }, + type: { + type: String, + default: 'PAGE' + }, + defaultParams: { + type: Object, + default: () => { + return { + pageIndex: 0, + pageSize: 12 + } + } } } as any, - setup(props: JTableProps ,{ slots, emit }){ + setup(props: JTableProps ,{ slots, emit, expose }){ const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE const _model = ref(props.model ? props.model : ModelEnum.CARD); // 模式切换 const column = ref(props.gridColumn || 4); @@ -162,25 +173,36 @@ const JTable = defineComponent({ const handleSearch = async (_params?: Record) => { loading.value = true if(props.request) { - const resp = await props.request({ + const resp = await props.request({ + pageIndex: 0, pageSize: 12, - pageIndex: 1, - ..._params + ...props.defaultParams, + ..._params, + terms: [ + ...(props.defaultParams?.terms || []), + ...(_params?.terms || []) + ] }) if(resp.status === 200){ - // 判断如果是最后一页且最后一页为空,就跳转到前一页 - if(resp.result?.data?.length === 0 && resp.result.total && resp.result.pageSize && resp.result.pageIndex) { - handleSearch({ - ..._params, - pageSize: pageSize.value, - pageIndex: pageIndex.value - 1, - }) + if(props.type === 'PAGE'){ + // 判断如果是最后一页且最后一页为空,就跳转到前一页 + if(resp.result.total && resp.result.pageSize && resp.result.pageIndex && resp.result?.data?.length === 0) { + handleSearch({ + ..._params, + pageSize: pageSize.value, + pageIndex: pageIndex.value > 0 ? pageIndex.value - 1 : 0, + }) + } else { + _dataSource.value = resp.result?.data || [] + pageIndex.value = resp.result?.pageIndex || 0 + pageSize.value = resp.result?.pageSize || 6 + total.value = resp.result?.total || 0 + } } else { - _dataSource.value = resp.result?.data || [] - pageIndex.value = resp.result?.pageIndex || 0 - pageSize.value = resp.result?.pageSize || 6 - total.value = resp.result?.total || 0 + _dataSource.value = resp?.result || [] } + } else { + _dataSource.value = [] } } else { _dataSource.value = props?.dataSource || [] @@ -188,9 +210,13 @@ const JTable = defineComponent({ loading.value = false } - watchEffect(() => { - handleSearch(props.params) - }) + watch( + () => props.params, + (newValue) => { + handleSearch(newValue) + }, + {deep: true, immediate: true} + ) onMounted(() => { window.onresize = () => { @@ -201,6 +227,23 @@ const JTable = defineComponent({ onUnmounted(() => { window.onresize = null }) + + /** + * 刷新数据 + * @param _params + */ + const reload = (_params?: Record) => { + handleSearch({ + ..._params, + pageSize: 12, + pageIndex: 0 + }) + } + + /** + * 导出方法 + */ + expose({ reload }) return () =>
@@ -233,7 +276,7 @@ const JTable = defineComponent({ onClose={() => { emit('cancelSelect') }} - closeText={取消选择} + closeText={取消选择} />
: null } @@ -282,7 +325,7 @@ const JTable = defineComponent({ {/* 分页 */} { - _dataSource.value.length && !props.noPagination && + (!!_dataSource.value.length) && !props.noPagination && props.type === 'PAGE' &&
({ current={pageIndex.value} pageSize={pageSize.value} pageSizeOptions={['12', '24', '48', '60', '100']} - showTotal={(total, range) => { - return `第 ${range[0]} - ${range[1]} 条/总共 ${total} 条` + showTotal={(num) => { + const minSize = pageIndex.value * pageSize.value + 1; + const MaxSize = (pageIndex.value + 1) * pageSize.value; + return `第 ${minSize} - ${MaxSize > num ? num : MaxSize} 条/总共 ${num} 条`; }} onChange={(page, size) => { handleSearch({ ...props.params, pageSize: size, - pageIndex: pageSize.value === size ? page : 1, + pageIndex: pageSize.value === size ? page : 0 }) }} /> diff --git a/src/components/Table/index.vue b/src/components/Table/index.vue index d596666f..d1fa47d2 100644 --- a/src/components/Table/index.vue +++ b/src/components/Table/index.vue @@ -41,7 +41,7 @@
- + diff --git a/src/views/link/AccessConfig/components/Media/GB28181.vue b/src/views/link/AccessConfig/components/Media/GB28181.vue new file mode 100644 index 00000000..239486ec --- /dev/null +++ b/src/views/link/AccessConfig/components/Media/GB28181.vue @@ -0,0 +1,818 @@ + + + + + diff --git a/src/views/link/AccessConfig/components/Media/index.vue b/src/views/link/AccessConfig/components/Media/index.vue index 75957aea..1f96e91d 100644 --- a/src/views/link/AccessConfig/components/Media/index.vue +++ b/src/views/link/AccessConfig/components/Media/index.vue @@ -1,30 +1,160 @@ diff --git a/src/views/link/AccessConfig/components/Network.vue b/src/views/link/AccessConfig/components/Network.vue index aa354d17..edd2c5c1 100644 --- a/src/views/link/AccessConfig/components/Network.vue +++ b/src/views/link/AccessConfig/components/Network.vue @@ -6,7 +6,7 @@
- + 选择与设备通信的网络组件
- + 使用选择的消息协议,对网络组件通信数据进行编解码、认证等操作
-
+
@@ -190,7 +195,6 @@ v-if="config.document" > -
@@ -281,11 +295,7 @@
- + 下一步 @@ -314,10 +324,9 @@ import { } from '../Detail/data'; import AccessCard from './AccessCard/index.vue'; import { message, Form } from 'ant-design-vue'; -import type { FormInstance } from 'ant-design-vue'; +import type { FormInstance, TableColumnType } from 'ant-design-vue'; import Markdown from 'vue3-markdown-it'; - - +import { QuestionCircleOutlined } from '@ant-design/icons-vue'; //测试数据1 const resultList1 = [ { @@ -363,13 +372,158 @@ const resultList1 = [ // metadata: '', // }; const result2 = { -"id": "MQTT", -"name": "MQTT", -"features": [], -"routes": [], -"document": "# MQTT认证说明\r\nCONNECT报文:\r\n```text\r\nclientId: 设备ID\r\nusername: secureId+\"|\"+timestamp\r\npassword: md5(secureId+\"|\"+timestamp+\"|\"+secureKey)\r\n ```\r\n\r\n说明: secureId以及secureKey在创建设备产品或设备实例时进行配置. \r\ntimestamp为当前系统时间戳(毫秒),与系统时间不能相差5分钟.\r\nmd5为32位,不区分大小写.", -"metadata": "{\"functions\":[],\"name\":\"test\",\"description\":\"测试用\",\"id\":\"test\",\"properties\":[{\"valueType\":{\"round\":\"HALF_UP\",\"type\":\"double\"},\"name\":\"温度\",\"id\":\"t\"},{\"valueType\":{\"round\":\"HALF_UP\",\"type\":\"int\"},\"name\":\"状态\",\"id\":\"state\"}],\"events\":[],\"tags\":[]}" -} + id: 'MQTT', + name: 'MQTT', + features: [ + { + id: 'supportFirmware', + name: '支持固件升级', + }, + ], + routes: [ + { + topic: '/{productId:产品ID}/{deviceId:设备ID}/properties/report', + upstream: true, + downstream: false, + qos: 0, + group: '属性上报', + description: '上报物模型属性数据', + example: '{"properties":{"属性ID":"属性值"}}', + address: '/{productId:产品ID}/{deviceId:设备ID}/properties/report', + }, + { + topic: '/{productId:产品ID}/{deviceId:设备ID}/properties/read', + upstream: false, + downstream: true, + qos: 0, + group: '读取属性', + description: '平台下发读取物模型属性数据指令', + example: + '{"messageId":"消息ID,回复时需要一致.","properties":["属性ID"]}', + address: '/{productId:产品ID}/{deviceId:设备ID}/properties/read', + }, + { + topic: '/{productId:产品ID}/{deviceId:设备ID}/properties/read/reply', + upstream: true, + downstream: false, + qos: 0, + group: '读取属性', + description: '对平台下发的读取属性指令进行响应', + example: + '{"messageId":"消息ID,与读取指令中的ID一致.","properties":{"属性ID":"属性值"}}', + address: + '/{productId:产品ID}/{deviceId:设备ID}/properties/read/reply', + }, + { + topic: '/{productId:产品ID}/{deviceId:设备ID}/properties/write', + upstream: false, + downstream: true, + qos: 0, + group: '修改属性', + description: '平台下发修改物模型属性数据指令', + example: + '{"messageId":"消息ID,回复时需要一致.","properties":{"属性ID":"属性值"}}', + address: '/{productId:产品ID}/{deviceId:设备ID}/properties/write', + }, + { + topic: '/{productId:产品ID}/{deviceId:设备ID}/properties/write/reply', + upstream: true, + downstream: false, + qos: 0, + group: '修改属性', + description: '对平台下发的修改属性指令进行响应', + example: + '{"messageId":"消息ID,与修改指令中的ID一致.","properties":{"属性ID":"属性值"}}', + address: + '/{productId:产品ID}/{deviceId:设备ID}/properties/write/reply', + }, + { + topic: '/{productId:产品ID}/{deviceId:设备ID}/event/{eventId:事件ID}', + upstream: true, + downstream: false, + qos: 0, + group: '事件上报', + description: '上报物模型事件数据', + example: '{"data":{"key":"value"}}', + address: + '/{productId:产品ID}/{deviceId:设备ID}/event/{eventId:事件ID}', + }, + { + topic: '/{productId:产品ID}/{deviceId:设备ID}/function/invoke', + upstream: false, + downstream: true, + qos: 0, + group: '调用功能', + description: '平台下发功能调用指令', + example: + '{"messageId":"消息ID,回复时需要一致.","functionId":"功能标识","inputs":[{"name":"参数名","value":"参数值"}]}', + address: '/{productId:产品ID}/{deviceId:设备ID}/function/invoke', + }, + { + topic: '/{productId:产品ID}/{deviceId:设备ID}/function/invoke/reply', + upstream: true, + downstream: false, + qos: 0, + group: '调用功能', + description: '设备响应平台下发的功能调用指令', + example: + '{"messageId":"消息ID,与下发指令中的messageId一致.","output":"输出结果,格式与物模型中定义的类型一致"', + address: + '/{productId:产品ID}/{deviceId:设备ID}/function/invoke/reply', + }, + { + topic: '/{productId:产品ID}/{deviceId:设备ID}/child/{childDeviceId:子设备ID}/{#:子设备相应操作的topic}', + upstream: true, + downstream: true, + qos: 0, + group: '子设备消息', + description: '网关上报或者平台下发子设备消息', + address: + '/{productId:产品ID}/{deviceId:设备ID}/child/{childDeviceId:子设备ID}/{#:子设备相应操作的topic}', + }, + { + topic: '/{productId:产品ID}/{deviceId:设备ID}/child-reply/{childDeviceId:子设备ID}/{#:子设备相应操作的topic}', + upstream: true, + downstream: true, + qos: 0, + group: '子设备消息', + description: '网关回复平台下发给子设备的指令结果', + address: + '/{productId:产品ID}/{deviceId:设备ID}/child-reply/{childDeviceId:子设备ID}/{#:子设备相应操作的topic}', + }, + { + topic: '/{productId:产品ID}/{deviceId:设备ID}/tags', + upstream: true, + downstream: false, + qos: 0, + group: '更新标签', + description: '更新标签数据', + example: '{"tags":{"key","value"}}', + address: '/{productId:产品ID}/{deviceId:设备ID}/tags', + }, + { + topic: '/{productId:产品ID}/{deviceId:设备ID}/online', + upstream: true, + downstream: false, + qos: 0, + group: '状态管理', + description: '设备上线', + address: '/{productId:产品ID}/{deviceId:设备ID}/online', + }, + { + topic: '/{productId:产品ID}/{deviceId:设备ID}/offline', + upstream: true, + downstream: false, + qos: 0, + group: '状态管理', + description: '设备离线', + address: '/{productId:产品ID}/{deviceId:设备ID}/offline', + }, + ], + document: + '### 认证说明\r\n\r\nCONNECT报文:\r\n```text\r\nclientId: 设备ID\r\nusername: secureId+"|"+timestamp\r\npassword: md5(secureId+"|"+timestamp+"|"+secureKey)\r\n ```\r\n\r\n说明: secureId以及secureKey在创建设备产品或设备实例时进行配置. \r\ntimestamp为当前时间戳(毫秒),与服务器时间不能相差5分钟.\r\nmd5为32位,不区分大小写.', + metadata: '', +}; function generateUUID() { var d = new Date().getTime(); @@ -400,6 +554,8 @@ const props = defineProps({ }, }); +const clientHeight = document.body.clientHeight; + const formRef = ref(); const useForm = Form.useForm; @@ -412,7 +568,7 @@ const allProcotolList = ref([]); const networkCurrent = ref(''); const procotolCurrent = ref(''); let config = ref({}); -let columnsMQTT = ref([]); +let columnsMQTT = ref([]); const form = reactive({ name: '', description: '', @@ -422,14 +578,18 @@ const { resetFields, validate, validateInfos } = useForm( form, reactive({ name: [ - { required: true, message: '请输入证书名称', trigger: 'blur' }, + { required: true, message: '请输入名称', trigger: 'blur' }, { max: 64, message: '最多可输入64个字符' }, ], }), ); -const queryNetworkList = async (id: string, params: object, data = {}) => { - const resp = await getNetworkList(NetworkTypeMapping.get(id), data, params); +const queryNetworkList = async (id: string, include: string, data = {}) => { + const resp = await getNetworkList( + NetworkTypeMapping.get(id), + include, + data, + ); if (resp.status === 200) { networkList.value = resp.result; } @@ -463,9 +623,7 @@ const addNetwork = () => { tab.onTabSaveSuccess = (value) => { if (value.success) { networkCurrent.value = value.result.id; - queryNetworkList(props.provider?.id, { - include: networkCurrent.value || '', - }); + queryNetworkList(props.provider?.id, networkCurrent.value || ''); } }; }; @@ -488,20 +646,14 @@ const checkedChange = (id: string) => { }; const networkSearch = (value: string) => { - queryNetworkList( - props.provider.id, - { - include: networkCurrent.value || '', - }, - { - terms: [ - { - column: 'name$LIKE', - value: `%${value}%`, - }, - ], - }, - ); + queryNetworkList(props.provider.id, networkCurrent.value || '', { + terms: [ + { + column: 'name$LIKE', + value: `%${value}%`, + }, + ], + }); }; const procotolChange = (id: string) => { if (!props.data.id) { @@ -576,7 +728,7 @@ const next = async () => { //使用测试数据2 config.value = result2; current.value = current.value + 1; - columnsMQTT = [ + columnsMQTT.value = [ { title: '分组', dataIndex: 'group', @@ -584,20 +736,24 @@ const next = async () => { ellipsis: true, align: 'center', width: 100, - customRender: (value, row, index) => { + customCell: (record: object, rowIndex: number) => { const obj = { - children: value, - attrs: {}, + children: record, + rowSpan: 0, }; - const list = (config && config.routes) || []; - const arr = list.filter((res) => { - return res.group == row.group; - }); - if (index == 0 || list[index - 1].group !== row.group) { - obj.attrs.rowSpan = arr.length; - } else { - obj.attrs.rowSpan = 0; - } + const list = + (config.value && config.value.routes) || []; + + const arr = list.filter( + (res: object) => res.group == record.group, + ); + + if ( + rowIndex == 0 || + list[rowIndex - 1].group !== record.group + ) + obj.rowSpan = arr.length; + return obj; }, }, @@ -605,6 +761,7 @@ const next = async () => { title: 'topic', dataIndex: 'topic', key: 'topic', + align: 'center', ellipsis: true, }, { @@ -614,7 +771,6 @@ const next = async () => { ellipsis: true, align: 'center', width: 100, - scopedSlots: { customRender: 'stream' }, }, { title: '说明', @@ -698,9 +854,7 @@ onMounted(() => { procotolCurrent.value = props.data.protocol; current.value = 0; networkCurrent.value = props.data.channelId; - queryNetworkList(props.provider.id, { - include: networkCurrent.value, - }); + queryNetworkList(props.provider.id, networkCurrent.value); procotolCurrent.value = props.data.protocol; steps.value = ['网络组件', '消息协议', '完成']; } else { @@ -711,9 +865,7 @@ onMounted(() => { } else { if (props.provider?.id) { if (props.provider.channel !== 'child-device') { - queryNetworkList(props.provider.id, { - include: '', - }); + queryNetworkList(props.provider.id, ''); steps.value = ['网络组件', '消息协议', '完成']; current.value = 0; } else { @@ -755,7 +907,6 @@ watch( } .card-last { padding-right: 5px; - max-height: 580px; overflow-y: auto; overflow-x: hidden; } diff --git a/src/views/link/Certificate/Detail/CertificateFile.vue b/src/views/link/Certificate/Detail/CertificateFile.vue index ae64c8f0..1bfe23c1 100644 --- a/src/views/link/Certificate/Detail/CertificateFile.vue +++ b/src/views/link/Certificate/Detail/CertificateFile.vue @@ -10,7 +10,7 @@ +
123
+ + + \ No newline at end of file diff --git a/src/views/northbound/DuerOS/index.vue b/src/views/northbound/DuerOS/index.vue new file mode 100644 index 00000000..f8ab376f --- /dev/null +++ b/src/views/northbound/DuerOS/index.vue @@ -0,0 +1,154 @@ + + + \ No newline at end of file diff --git a/src/views/notice/Config/Detail/components/EditTable.vue b/src/views/notice/Config/Detail/components/EditTable.vue index 29936827..853fc48f 100644 --- a/src/views/notice/Config/Detail/components/EditTable.vue +++ b/src/views/notice/Config/Detail/components/EditTable.vue @@ -1,65 +1,63 @@ - + diff --git a/src/views/notice/Config/types.d.ts b/src/views/notice/Config/types.d.ts index 4c0f1d6f..c9ade1d7 100644 --- a/src/views/notice/Config/types.d.ts +++ b/src/views/notice/Config/types.d.ts @@ -1,37 +1,70 @@ -interface IHeaders { +export interface IHeaders { + id?: number; key: string; value: string; } +export interface IConfiguration { + // 钉钉 + appKey?: string; + appSecret?: string; + url?: string; + // 微信 + corpId?: string; + corpSecret?: string; + // 邮件 + host?: string; + port?: number; + ssl?: boolean; + sender?: string; + username?: string; + password?: string; + // 语音 + regionId?: string; + accessKeyId?: string; + secret?: string; + // 短信 + regionId?: string; + accessKeyId?: string; + secret?: string; + // webhook + // url?: string; + headers?: IHeaders[]; +} export type ConfigFormData = { - configuration: { - // 钉钉 - appKey?: string; - appSecret?: string; - url?: string; - // 微信 - corpId?: string; - corpSecret?: string; - // 邮件 - host?: string; - port?: number; - ssl?: boolean; - sender?: string; - username?: string; - password?: string; - // 语音 - regionId?: string; - accessKeyId?: string; - secret?: string; - // 短信 - regionId?: string; - accessKeyId?: string; - secret?: string; - // webhook - // url?: string; - headers?: IHeaders[]; - }; + configuration: IConfiguration; + // configuration: { + // // 钉钉 + // appKey?: string; + // appSecret?: string; + // url?: string; + // // 微信 + // corpId?: string; + // corpSecret?: string; + // // 邮件 + // host?: string; + // port?: number; + // ssl?: boolean; + // sender?: string; + // username?: string; + // password?: string; + // // 语音 + // regionId?: string; + // accessKeyId?: string; + // secret?: string; + // // 短信 + // regionId?: string; + // accessKeyId?: string; + // secret?: string; + // // webhook + // // url?: string; + // headers?: IHeaders[]; + // }; description: string; name: string; provider: string; type: string; + id?: string; + maxRetryTimes?: number; + creatorId?: string; + createTime?: number; }; diff --git a/src/views/notice/Template/Detail/components/Attachments.vue b/src/views/notice/Template/Detail/components/Attachments.vue new file mode 100644 index 00000000..a3dbe211 --- /dev/null +++ b/src/views/notice/Template/Detail/components/Attachments.vue @@ -0,0 +1,105 @@ + + + + + + diff --git a/src/views/notice/Template/Detail/doc/AliyunSms.tsx b/src/views/notice/Template/Detail/doc/AliyunSms.tsx new file mode 100644 index 00000000..e44a7e40 --- /dev/null +++ b/src/views/notice/Template/Detail/doc/AliyunSms.tsx @@ -0,0 +1,43 @@ +import './index.less'; + +const AliyunSms = () => { + return ( +
+
+ 阿里云短信服务平台: + + https://dysms.console.aliyun.com + +
+

1. 概述

+
+ 通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。 + 使用阿里云短信时需先在阿里云短信服务平台创建短信模板。 +
+

2.模板配置说明

+ +
+

1、绑定配置

+
使用固定的通知配置发送此通知模板
+

2、模板

+
阿里云短信平台自定义的模板名称
+

3、收信人

+
+ {' '} + 当前仅支持国内手机号,此处若不填,则在模板调试和配置告警通知时手动填写 +
+

4、签名

+
用于短信内容签名信息显示,需在阿里云短信进行配置。
+

5、变量属性

+
+ 需要在当前页面手动设置与阿里云短信模板中一样的变量,否则会导致发送异常。 +
+
+
+ ); +}; +export default AliyunSms; diff --git a/src/views/notice/Template/Detail/doc/AliyunVoice.tsx b/src/views/notice/Template/Detail/doc/AliyunVoice.tsx new file mode 100644 index 00000000..0ef4ba6b --- /dev/null +++ b/src/views/notice/Template/Detail/doc/AliyunVoice.tsx @@ -0,0 +1,43 @@ +import './index.less'; + +const AliyunVoice = () => { + return ( +
+
+ 阿里云语音服务平台: + + https://account.console.aliyun.com + +
+

1. 概述

+
+ 通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。 + 使用阿里云语音时需先在阿里云语音服务平台创建语音模板。 +
+

2.模板配置说明

+
+

1、绑定配置

+
使用固定的通知配置发送此通知模板
+

2、类型

+
阿里云语音通知类型,当类型为验证码类型时可配置变量。
+

3、模板ID

+
阿里云语音对每一条语音通知分配的唯一ID标识
+

4、被叫号码

+
当前仅支持国内手机号,此处若不填,则在模板调试和配置告警通知时手动填写。
+
若您使用的语音通知文件为公共模式外呼,则该参数值不填。
+
若您使用的语音通知文件为专属模式外呼,则必须传入已购买的号码,仅支持一个号码。
+

5、被叫显号

+
用户呼叫号码显示,必须是在阿里云购买的号码。
+

6、播放次数

+
最多可播放3次
+

7、模板内容

+
+ 仅当通知类型为验证码类型时可进行配置,变量标识需要阿里云模板中的标识一致,支持填写带变量的动态模板。 + 变量填写规范示例:${'{name}'} + 。填写动态参数后,可对变量的名称、类型、格式进行配置,以便告警通知是填写。 +
+
+
+ ); +}; +export default AliyunVoice; diff --git a/src/views/notice/Template/Detail/doc/DingTalk.tsx b/src/views/notice/Template/Detail/doc/DingTalk.tsx new file mode 100644 index 00000000..387fb2b0 --- /dev/null +++ b/src/views/notice/Template/Detail/doc/DingTalk.tsx @@ -0,0 +1,54 @@ + +import './index.less'; +import { Image } from 'ant-design-vue'; +import { getImage } from '@/utils/comm'; + +const DingTalk = () => { + const agentId = getImage('/notice/doc/template/dingTalk-message/01-Agentid.jpg'); + // const userId = getImage('/notice/doc/template/dingTalk-message/02-user-id.jpg'); + // const dept = getImage('/notice/doc/template/dingTalk-message/03-dept.jpg'); + const a = '{name}'; + return ( +
+
+ 钉钉开放平台: + + https://open-dev.dingtalk.com + +
+ 钉钉管理后台: + + https://www.dingtalk.com + +
+

1. 概述

+
+ 通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。 +
使用钉钉消息通知时需在钉钉开放平台中创建好对应的应用
+
+

2.模板配置说明

+

1、绑定配置

+
使用固定的通知配置发送此通知模板
+

2、Agentid

+
应用唯一标识
+
获取路径:“钉钉开放平台”--“应用开发”--“查看应用”
+
+ +
+

3、收信人、收信部门

+
若不填写收信人,则在模板调试和配置告警通知时手动填写。
+ {/*
收信人ID获取路径:“钉钉管理后台”--“通讯录”--“查看用户”
*/} + {/*
收信部门ID获取路径:“钉钉管理后台”--“通讯录”--“编辑部门”
*/} + {/*
*/} + {/* */} + {/* */} + {/*
*/} +

4、模板内容

+
+ 支持填写带变量的动态模板。变量填写规范示例:${a} + 。填写动态参数后,可对变量的名称、类型、格式进行配置,以便告警通知时填写。 +
+
+ ); +}; +export default DingTalk; diff --git a/src/views/notice/Template/Detail/doc/DingTalkRebot.tsx b/src/views/notice/Template/Detail/doc/DingTalkRebot.tsx new file mode 100644 index 00000000..29f9a107 --- /dev/null +++ b/src/views/notice/Template/Detail/doc/DingTalkRebot.tsx @@ -0,0 +1,35 @@ +import './index.less'; + +const DingTalkRebot = () => { + const b = '{name}'; + return ( +
+
+ 钉钉开放平台: + + https://open-dev.dingtalk.com + +
+

1. 概述

+
+ 通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。 +
+
+ 使用钉钉群机器人消息通知时需在钉钉开放平台中创建好对应的机器人,再到钉钉客户端在对应的群中绑定智能机器人。 +
+

2.模板配置说明

+
+

1、绑定配置

+
使用固定的通知配置发送此通知模板
+

2、消息类型

+
目前支持text、markdown、link3种。
+

3、模板内容

+
+ 支持填写带变量的动态模板。变量填写规范示例:${b} + 。填写动态参数后,可对变量的名称、类型、格式进行配置,以便告警通知时填写。 +
+
+
+ ); +}; +export default DingTalkRebot; diff --git a/src/views/notice/Template/Detail/doc/Email.tsx b/src/views/notice/Template/Detail/doc/Email.tsx new file mode 100644 index 00000000..4278b66a --- /dev/null +++ b/src/views/notice/Template/Detail/doc/Email.tsx @@ -0,0 +1,30 @@ +import './index.less'; + +const Email = () => { + const a = '{标题}'; + const b = '{name}'; + return ( +
+

1. 概述

+
+ 通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。 + 服务器地址支持自定义输入。 +
+

2.模板配置说明

+
+ {/*

1、服务器地址

+
服务器地址支持自定义输入
*/} +

1、标题

+
支持输入变量,变量格式${a}
+

2、收件人

+
支持录入多个邮箱地址,可填写变量参数。
+

3、模板内容

+
+ 支持填写带变量的动态模板。变量填写规范示例:${b} + 。填写动态参数后,可对变量的名称、类型、格式进行配置,以便告警通知时填写。 +
+
+
+ ); +}; +export default Email; diff --git a/src/views/notice/Template/Detail/doc/Webhook.tsx b/src/views/notice/Template/Detail/doc/Webhook.tsx new file mode 100644 index 00000000..ee0d2fff --- /dev/null +++ b/src/views/notice/Template/Detail/doc/Webhook.tsx @@ -0,0 +1,18 @@ +import './index.less'; + +const Webhook = () => { + return ( +
+

1. 概述

+
+ 通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。 +
+

2.模板配置说明

+
+ 1、请求体 请求体中的数据来自于发送通知时指定的所有变量,也可通过自定义的方式进行变量配置。 + 使用webhook通知时,系统会将该事件通过您指定的URL地址,以POST方式发送。 +
+
+ ); +}; +export default Webhook; diff --git a/src/views/notice/Template/Detail/doc/WeixinApp.tsx b/src/views/notice/Template/Detail/doc/WeixinApp.tsx new file mode 100644 index 00000000..d441e4db --- /dev/null +++ b/src/views/notice/Template/Detail/doc/WeixinApp.tsx @@ -0,0 +1,58 @@ +import './index.less'; +import { Image } from 'ant-design-vue'; +import { getImage } from '@/utils/comm'; + +const WeixinApp = () => { + const appId = getImage('/notice/doc/template/weixin-official/02-mini-Program-Appid.png'); + + return ( +
+
+ 企业微信管理后台: + + https://work.weixin.qq.com + +
+

1. 概述

+
+ 通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。 +
+

2.模板配置说明

+
+

1、绑定配置

+
使用固定的通知配置发送此通知模板
+
+
+

2、用户标签

+
以标签的维度通知该标签下所有用户
+
+
+

3、消息模板

+
微信公众号中配置的消息模板
+
+
+

4、模板跳转链接

+
点击消息之后进行页面跳转
+
+
+

5、跳转小程序Appid

+
点击消息之后打开对应的小程序
+
+
+

6、跳转小程序具体路径

+
点击消息之后跳转到小程序的具体页面
+
+ +
+
+
+

7、模板内容

+
+ 支持填写带变量的动态模板。变量填写规范示例:${name} + 。填写动态参数后,可对变量的名称、类型、格式进行配置,以便告警通知时填写。 +
+
+
+ ); +}; +export default WeixinApp; diff --git a/src/views/notice/Template/Detail/doc/WeixinCorp.tsx b/src/views/notice/Template/Detail/doc/WeixinCorp.tsx new file mode 100644 index 00000000..5f547d7b --- /dev/null +++ b/src/views/notice/Template/Detail/doc/WeixinCorp.tsx @@ -0,0 +1,48 @@ +import './index.less'; +import { Image } from 'ant-design-vue'; +import { getImage } from '@/utils/comm'; + +const WeixinCorp = () => { + const agentId = getImage('/notice/doc/template/weixin-corp/01-Agentid.jpg'); + const userId = getImage('/notice/doc/template/weixin-corp/02-userID.jpg'); + const toDept = getImage('/notice/doc/template/weixin-corp/03-toDept.jpg'); + const toTags = getImage('/notice/doc/template/weixin-corp/04-toTags.jpg'); + + return ( +
+
+ 企业微信管理后台: + + https://work.weixin.qq.com + +
+

1. 概述

+
+ 通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。 +
+

2.模版配置说明

+
+

1、绑定配置

+
使用固定的通知配置发送此通知模板
+

2、Agentid

+
应用唯一标识
+
获取路径:“企业微信”管理后台--“应用管理”--“应用”--“查看应用”
+
+ +
+

3、收信人ID、收信部门ID、标签推送

+
+ 接收通知的3种方式,3个字段若在此页面都没有填写,则在模板调试和配置告警通知时需要手动填写 +
+
收信人ID获取路径:【通讯录】-{'>'}【成员信息】查看成员账号
+
收信组织ID获取路径:【通讯录】-{'>'}【部门信息】查看部门ID
+
+ + + +
+
+
+ ); +}; +export default WeixinCorp; diff --git a/src/views/notice/Template/Detail/doc/index.less b/src/views/notice/Template/Detail/doc/index.less new file mode 100644 index 00000000..1b61258c --- /dev/null +++ b/src/views/notice/Template/Detail/doc/index.less @@ -0,0 +1,39 @@ +.doc { + height: 750px; + padding: 24px; + overflow-y: auto; + color: rgba(#000, 0.8); + font-size: 14px; + background-color: #fafafa; + + .url { + padding: 8px 16px; + color: #2f54eb; + background-color: rgba(#a7bdf7, 0.2); + } + + h1 { + margin: 16px 0; + color: rgba(#000, 0.85); + font-weight: bold; + font-size: 14px; + + &:first-child { + margin-top: 0; + } + } + + h2 { + margin: 6px 0; + color: rgba(0, 0, 0, 0.8); + font-size: 14px; + } + span { + color: rgba(0, 0, 0, 0.8); + font-weight: 600; + } + + .image { + margin: 16px 0; + } +} diff --git a/src/views/notice/Template/Detail/doc/index.tsx b/src/views/notice/Template/Detail/doc/index.tsx new file mode 100644 index 00000000..3069e35e --- /dev/null +++ b/src/views/notice/Template/Detail/doc/index.tsx @@ -0,0 +1,46 @@ +import DingTalk from './DingTalk'; +import DingTalkRebot from './DingTalkRebot'; +import AliyunSms from './AliyunSms'; +import AliyunVoice from './AliyunVoice'; +import Email from './Email'; +import Webhook from './Webhook'; +import WeixinApp from './WeixinApp'; +import WeixinCorp from './WeixinCorp'; + +export default defineComponent({ + name: 'Doc', + props: { + docData: { + type: Object, + default: () => ({}), + }, + }, + setup(props) { + const docMap = { + weixin: { + corpMessage: , + officialMessage: , + }, + dingTalk: { + dingTalkMessage: , + dingTalkRobotWebHook: , + }, + voice: { + aliyun: , + }, + sms: { + aliyunSms: , + }, + email: { + embedded: , + }, + webhook: { + http: , + }, + }; + + return () => ( + docMap?.[props.docData.type]?.[props.docData.provider] + ) + }, +}); diff --git a/src/views/notice/Template/Detail/index.vue b/src/views/notice/Template/Detail/index.vue index c2a1ee68..b281c75b 100644 --- a/src/views/notice/Template/Detail/index.vue +++ b/src/views/notice/Template/Detail/index.vue @@ -1,8 +1,596 @@ - + + + diff --git a/src/views/notice/Template/index.vue b/src/views/notice/Template/index.vue index 93e4d46b..3201947b 100644 --- a/src/views/notice/Template/index.vue +++ b/src/views/notice/Template/index.vue @@ -3,6 +3,20 @@
通知模板
- + diff --git a/src/views/notice/Template/types.d.ts b/src/views/notice/Template/types.d.ts new file mode 100644 index 00000000..54bea847 --- /dev/null +++ b/src/views/notice/Template/types.d.ts @@ -0,0 +1,74 @@ +export interface IHeaders { + id?: number; + key: string; + value: string; +} + +interface IAttachments { + location: string; + name: string; + id?: number; +} +interface IVariableDefinitions { + id: string; + name: string; + type: string; + format: string; +} + +export type TemplateFormData = { + template: { + // 钉钉消息 + agentId?: string; + message?: string; + // 钉钉机器人 + messageType?: string; + markdown?: { + text: string; + title: string; + }; + link?: { + title: string; + picUrl: string; + messageUrl: string; + text: string; + }; + // 微信 + // agentId?: string; + // message?: string; + toParty?: string; + toUser?: string; + toTag?: string; + // 邮件 + subject?: string; + sendTo?: string[]; + attachments?: IAttachments[]; + // message?: string; + text?: string; + // 语音 + templateType?: string; + templateCode?: string; + ttsCode?: string; + // message?: string; + playTimes?: number; + calledShowNumbers?: string; + calledNumber?: string; + // 短信 + code?: string; + // message?: string; + phoneNumber?: string; + signName?: string; + // webhook + contextAsBody?: boolean; + body?: string; + }; + name: string; + type: string; + provider: string; + description: string; + variableDefinitions: IVariableDefinitions[]; + id?: string; + creatorId?: string; + createTime?: number; + configId?: string; +}; \ No newline at end of file diff --git a/src/views/notice/const.ts b/src/views/notice/const.ts index 47a86c03..9bd71ca6 100644 --- a/src/views/notice/const.ts +++ b/src/views/notice/const.ts @@ -33,7 +33,7 @@ export const NOTICE_METHOD: INoticeMethod[] = [ }, ]; -// 消息类型 +// 类型 export const MSG_TYPE = { dingTalk: [ { @@ -93,34 +93,130 @@ export const MSG_TYPE = { // 配置 export const CONFIG_FIELD_MAP = { dingTalk: { - appKey: undefined, - appSecret: undefined, - url: undefined, + dingTalkMessage: { + appKey: '', + appSecret: '', + }, + dingTalkRobotWebHook: { + url: '', + } }, weixin: { - corpId: undefined, - corpSecret: undefined, + corpMessage: { + corpId: '', + corpSecret: '', + }, + // officialMessage: {}, }, email: { - host: undefined, - port: 25, - ssl: false, - sender: undefined, - username: undefined, - password: undefined, + embedded: { + host: '', + port: 25, + ssl: false, + sender: '', + username: '', + password: '', + } }, voice: { - regionId: undefined, - accessKeyId: undefined, - secret: undefined, + aliyun: { + regionId: '', + accessKeyId: '', + secret: '', + } }, sms: { - regionId: undefined, - accessKeyId: undefined, - secret: undefined, + aliyunSms: { + regionId: '', + accessKeyId: '', + secret: '', + } }, webhook: { - url: undefined, - headers: [], + http: { + url: undefined, + headers: [], + } }, -}; \ No newline at end of file + +}; + +// 模板 +export const TEMPLATE_FIELD_MAP = { + dingTalk: { + dingTalkMessage: { + agentId: '', + message: '', + }, + dingTalkRobotWebHook: { + message: '', + messageType: '', + markdown: { + text: '', + title: '', + }, + link: { + title: '', + picUrl: '', + messageUrl: '', + text: '', + }, + } + }, + weixin: { + corpMessage: { + agentId: '', + message: '', + toParty: '', + toUser: '', + toTag: '', + }, + officialMessage: {}, + }, + email: { + embedded: { + subject: '', + sendTo: [], + attachments: [], + message: '', + text: '', + } + }, + voice: { + aliyun: { + templateType: '', + templateCode: '', + ttsCode: '', + message: '', + playTimes: undefined, + calledShowNumbers: '', + calledNumber: '', + } + }, + sms: { + aliyunSms: { + code: '', + message: '', + phoneNumber: '', + signName: '', + } + }, + webhook: { + http: { + contextAsBody: true, + body: '' + } + }, +}; + +// 钉钉机器人-消息类型 +export const ROBOT_MSG_TYPE = [ + { label: 'markdown', value: 'markdown' }, + { label: 'text', value: 'text' }, + { label: 'link', value: 'link' }, +] +// 语音通知类型 +export const VOICE_TYPE = [ + { label: '语音通知', value: 'voice' }, + { label: '语音验证码', value: 'tts' }, +] \ No newline at end of file diff --git a/src/views/system/Basis/index.d.ts b/src/views/system/Basis/index.d.ts new file mode 100644 index 00000000..29c40c1d --- /dev/null +++ b/src/views/system/Basis/index.d.ts @@ -0,0 +1,37 @@ +import type { Rule } from 'ant-design-vue/es/form'; + +/**基本信息表单 */ +export interface formValueType { + title: string; // 系统名称 + headerTheme: string; // 主题色 + apiKey: string; // 高德 API key + 'base-path': string; // 系统后台访问的URL + logo:string, + ico:string, + backgroud:string +} + +export interface formType { + formValue: formValueType, + rulesFrom: Record + logoLoading: boolean, + backLoading: boolean, + iconLoading: boolean, + saveLoading: boolean, + clickSave?: Function, + getDetails: Function +} + +/** +* 图片上传表单 +*/ +export interface uploaderType { + imageTypes: Array; + iconTypes: Array, + beforeLogoUpload: (file: UploadProps['beforeUpload']) => void + handleChangeLogo: (info: UploadChangeParam) => void + beforeBackUpload: (file: UploadProps['beforeUpload']) => void + changeBackUpload: (info: UploadChangeParam) => void + beforeIconUpload: (file: UploadProps['beforeUpload']) => void + changeIconUpload: (info: UploadChangeParam) => void +} diff --git a/src/views/system/Basis/index.vue b/src/views/system/Basis/index.vue new file mode 100644 index 00000000..547ec67f --- /dev/null +++ b/src/views/system/Basis/index.vue @@ -0,0 +1,627 @@ + + + + + diff --git a/src/views/system/Role/Detail/Permiss/index.vue b/src/views/system/Role/Detail/Permiss/index.vue new file mode 100644 index 00000000..74a4720e --- /dev/null +++ b/src/views/system/Role/Detail/Permiss/index.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/src/views/system/Role/Detail/User/index.vue b/src/views/system/Role/Detail/User/index.vue new file mode 100644 index 00000000..30586c85 --- /dev/null +++ b/src/views/system/Role/Detail/User/index.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/src/views/system/Role/Detail/components/AddUserDialog.vue b/src/views/system/Role/Detail/components/AddUserDialog.vue new file mode 100644 index 00000000..7a3a4ee3 --- /dev/null +++ b/src/views/system/Role/Detail/components/AddUserDialog.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/src/views/system/Role/Detail/components/PermissTree.vue b/src/views/system/Role/Detail/components/PermissTree.vue new file mode 100644 index 00000000..b47647ad --- /dev/null +++ b/src/views/system/Role/Detail/components/PermissTree.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/src/views/system/Role/Detail/index.vue b/src/views/system/Role/Detail/index.vue new file mode 100644 index 00000000..1c58b369 --- /dev/null +++ b/src/views/system/Role/Detail/index.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/src/views/system/Role/components/AddDialog.vue b/src/views/system/Role/components/AddDialog.vue new file mode 100644 index 00000000..33e817b3 --- /dev/null +++ b/src/views/system/Role/components/AddDialog.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/src/views/system/Role/index.vue b/src/views/system/Role/index.vue new file mode 100644 index 00000000..11758cdd --- /dev/null +++ b/src/views/system/Role/index.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/src/views/user/Login/index.vue b/src/views/user/Login/index.vue index 1b1d8968..d41703b3 100644 --- a/src/views/user/Login/index.vue +++ b/src/views/user/Login/index.vue @@ -5,8 +5,16 @@
@@ -16,11 +24,13 @@
-
物联网平台
+
+ {{ basis.title || SystemConst.SYSTEM_NAME }} +
(); +const basis = ref({}); const defaultImg = getImage('/apply/provider1.png'); const iconMap = new Map(); @@ -222,6 +235,15 @@ const onFinish = async () => { username: form.username, }); LocalStore.set(TOKEN_KEY, res?.result.token); + // if (res.result.username === 'admin') { + // const resp: any = await getInitSet(); + // if (resp.status === 200 && !resp.result.length) { + // window.location.href = '/#/init-home'; + // return; + // } + // } + // window.location.href = '/'; + const resp: any = await getInitSet(); if (resp.success) { router.push('/demo'); @@ -257,6 +279,7 @@ const getCookie = () => { }; const getOpen = () => { + LocalStore.removeAll(); systemVersion().then((res: any) => { if (res.success && res.result) { LocalStore.set(Version_Code, res.result.edition); @@ -269,6 +292,18 @@ const getOpen = () => { } } }); + settingDetail('front').then((res) => { + if (res.status === 200) { + const ico: any = document.querySelector('link[rel="icon"]'); + ico.href = res.result.ico; + basis.value = res.result; + if (res.result.title) { + document.title = res.result.title; + } else { + document.title = ''; + } + } + }); }; const handleClickOther = (item: any) => { @@ -316,6 +351,14 @@ screenRotation(screenWidth.value, screenHeight.value); .left { width: 73%; height: 100%; + + .records { + position: absolute; + top: 96%; + left: 35%; + color: rgba(0, 0, 0, 0.35); + font-size: 14px; + } } .right {