diff --git a/src/api/initHome.ts b/src/api/initHome.ts index 60fa7e56..658a3394 100644 --- a/src/api/initHome.ts +++ b/src/api/initHome.ts @@ -1,15 +1,15 @@ import server from '@/utils/request'; // 更新全部菜单 -export const updateMenus = (data: any) => server +export const updateMenus = (data: any) => server.path(`/menu/iot/_all`, data) // 添加角色 -export const addRole = (data: any) => server.post(`/role`) +export const addRole = (data: any) => server.post(`/role`, data) //更新权限菜单 export const getRoleMenu = (id: string) => server.get(`/menu/role/${id}/_grant/tree`) //更新权限菜单 -export const updateRoleMenu = (id: string, data: any) => server.put(`/menu/role/${id}/_grant`) +export const updateRoleMenu = (id: string, data: any) => server.put(`/menu/role/${id}/_grant`, data) // 记录初始化 export const saveInit = () => server.post(`/user/settings/init`,{ init: true },) @@ -22,4 +22,34 @@ export const getInit = () => server.get(`/user/settings/init`) export const getSystemPermission = () =>server.get(`/system/resources/permission`) // 保存基础信息 - export const save = (data?: any) => server.post('/system/config/scope/_save') \ No newline at end of file + export const save = (data?: any) => server.post('/system/config/scope/_save',data) + + // 查询对应协议下的本地端口数据 + export const getResourcesCurrent = () => server.get('/network/resources/alive/_current') + +// 保存网络组件 +export const saveNetwork = (data: any) => server.post(`/network/config`, data) + +// 保存协议 +export const saveProtocol = () => server.post(`/protocol/default-protocol/_save`,) + +// 新增设备接入网关 +export const saveAccessConfig = (data: any) => server.post(`/gateway/device`, data) + +// 新增产品 +export const saveProduct = (data: any) => server.post(`/device/product`,data) + +// 新增设备 +export const saveDevice = (data: any) => server.post(`/device/instance`,data) + +// 启用设备 +export const deployDevice = (deviceId: string, params?: any) => server.post(`/device-instance/${deviceId}/deploy`,params,) +export const changeDeploy= (id: string) => server.post(`/device-product/${id}/deploy`) + +// 查询保存后的数据 +export const detail = (data?: any) => server.post(`/system/config/scopes`, data) + +// 获取协议 +export const getProtocol = () =>server.get(`/protocol/_query/no-paging?paging=false`) + +// 上传文件 diff --git a/src/api/iot-card/home.ts b/src/api/iot-card/home.ts new file mode 100644 index 00000000..d8b5e291 --- /dev/null +++ b/src/api/iot-card/home.ts @@ -0,0 +1,6 @@ +import server from '@/utils/request' + +// 查询特定天数流量数据 +export const queryFlow = (beginTime: any, endTime: any, data: any) => server.post(`/network/flow/_query/${beginTime}/${endTime}`, data) + +export const list = (data: any) => server.post(`/network/card/_query`, data) \ No newline at end of file diff --git a/src/components/CardBox/index.vue b/src/components/CardBox/index.vue index 1cc26f6e..4da3e72c 100644 --- a/src/components/CardBox/index.vue +++ b/src/components/CardBox/index.vue @@ -54,31 +54,24 @@ delete: item.key === 'delete', }" > + - - - - - - - + + + {{ item.text }} - - + + - - - - - - - + + + {{ item.text }} - - + + @@ -291,10 +284,10 @@ const handleClick = () => { display: flex; flex-grow: 1; - > span, - & button { - width: 100%; - border-radius: 0; + & > span, + button { + width: 100% !important; + border-radius: 0 !important; } button { @@ -372,9 +365,9 @@ const handleClick = () => { } } - :deep(.ant-tooltip-disabled-compatible-wrapper) { - width: 100%; - } + // :deep(.ant-tooltip-disabled-compatible-wrapper) { + // width: 100%; + // } } } } diff --git a/src/components/Table/index.tsx b/src/components/Table/index.tsx index 38e2e6af..915657c7 100644 --- a/src/components/Table/index.tsx +++ b/src/components/Table/index.tsx @@ -50,11 +50,20 @@ export interface JTableProps extends TableProps{ pageIndex: number; }; model?: keyof typeof ModelEnum | undefined; // 显示table还是card - actions?: ActionsType[]; + // actions?: ActionsType[]; noPagination?: boolean; rowSelection?: TableProps['rowSelection']; cardProps?: Record; dataSource?: Record[]; + gridColumn: number; + /** + * 用于不同分辨率 + * gridColumns[0] 1366 ~ 1440 分辨率; + * gridColumns[1] 1440 ~ 1600 分辨率; + * gridColumns[2] > 1600 分辨率; + */ + gridColumns?: number[]; + alertRender?: boolean; } const JTable = defineComponent({ @@ -87,10 +96,10 @@ const JTable = defineComponent({ type: [String, undefined], default: undefined }, - actions: { - type: Array as PropType, - default: () => [] - }, + // actions: { + // type: Array as PropType, + // default: () => [] + // }, noPagination: { type: Boolean, default: false @@ -106,12 +115,24 @@ const JTable = defineComponent({ dataSource: { type: Array, default: () => [] + }, + gridColumns: { + type: Array as PropType, + default: [2, 3, 4] + }, + gridColumn: { + type: Number, + default: 4 + }, + alertRender: { + type: Boolean, + default: true } } as any, setup(props: JTableProps ,{ slots, emit }){ const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE const _model = ref(props.model ? props.model : ModelEnum.CARD); // 模式切换 - const column = ref(4); + const column = ref(props.gridColumn || 4); const _dataSource = ref[]>([]) const pageIndex = ref(0) const pageSize = ref(6) @@ -119,9 +140,20 @@ const JTable = defineComponent({ const _columns = ref(props?.columns || []) const loading = ref(true) - // alert关闭,取消选择 - const handleAlertClose = () => { - emit('cancelSelect') + /** + * 监听宽度,计算显示卡片个数 + */ + const windowChange = () => { + if (window.innerWidth <= 1440) { + const _column = props.gridColumn && props.gridColumn < 2 ? props.gridColumn : 2; + column.value = props.gridColumns ? props.gridColumns[0] : _column + } else if (window.innerWidth > 1440 && window.innerWidth <= 1600) { + const _column = props.gridColumn && props.gridColumn < 3 ? props.gridColumn : 3; + column.value = props.gridColumns ? props.gridColumns[1] : _column + } else if (window.innerWidth > 1600) { + const _column = props.gridColumn && props.gridColumn < 4 ? props.gridColumn : 4; + column.value = props.gridColumns ? props.gridColumns[2] : _column + } } /** @@ -153,13 +185,22 @@ const JTable = defineComponent({ } else { _dataSource.value = props?.dataSource || [] } - loading.value = false } watchEffect(() => { handleSearch(props.params) }) + + onMounted(() => { + window.onresize = () => { + windowChange() + } + }) + + onUnmounted(() => { + window.onresize = null + }) return () => @@ -184,12 +225,14 @@ const JTable = defineComponent({ {/* content */} { - props?.rowSelection && props?.rowSelection?.selectedRowKeys && props.rowSelection.selectedRowKeys?.length ? + props.alertRender && props?.rowSelection && props?.rowSelection?.selectedRowKeys && props.rowSelection.selectedRowKeys?.length ? { + emit('cancelSelect') + }} closeText={取消选择} /> : null @@ -205,8 +248,10 @@ const JTable = defineComponent({ > { _dataSource.value.map(item => slots.card ? - {slots.card({row: item, actions: props?.actions || []})} - : null) + + {slots.card(item)} + : null + ) } : @@ -225,7 +270,7 @@ const JTable = defineComponent({ const {column, record} = dt; if((column?.key || column?.dataIndex) && column?.scopedSlots && (slots?.[column?.dataIndex] || slots?.[column?.key])) { const _key = column?.key || column?.dataIndex - return slots?.[_key]!({row: record, actions: props.actions}) + return slots?.[_key]!(record) } else { return record?.[column?.dataIndex] || '' } diff --git a/src/components/Table/index.vue b/src/components/Table/index.vue index 274d5d3b..d596666f 100644 --- a/src/components/Table/index.vue +++ b/src/components/Table/index.vue @@ -15,7 +15,7 @@ - + 取消选择 diff --git a/src/global.d.ts b/src/global.d.ts index f4cee181..78d66823 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -9,4 +9,5 @@ declare module '*.jpeg'; declare module '*.gif'; declare module '*.bmp'; declare module '*.js'; -declare module '*.ts'; \ No newline at end of file +declare module '*.ts'; +declare module 'js-cookie'; \ No newline at end of file diff --git a/src/router/menu.ts b/src/router/menu.ts index 6936fb5f..a94c47f0 100644 --- a/src/router/menu.ts +++ b/src/router/menu.ts @@ -87,9 +87,19 @@ export default [ path: '/link/accessConfig/detail/add', component: () => import('@/views/link/AccessConfig/Detail/index.vue') }, + // system 系统管理 + { + path:'/system/api', + components: ()=>import('@/views/system/apiPage/index') + }, // 初始化 { - path: '/init-home', - component: () => import('@/views/init-home/index.vue') + path: '/init-home', + component: () => import('@/views/init-home/index.vue') + }, + // 物联卡 iot-card + { + path: '/iot-card/home', + component: () => import('@/views/iot-card/Home/index.vue') }, ] \ No newline at end of file diff --git a/src/utils/consts.ts b/src/utils/consts.ts index d24a68c3..93c2cc39 100644 --- a/src/utils/consts.ts +++ b/src/utils/consts.ts @@ -29,38 +29,21 @@ export const StatusColorEnum = { 'default': 'default', } -class SystemConst { - static API_BASE = 'api'; - - static SYSTEM_NAME = 'Jetlinks'; - - static LOGIN = 'LOGIN-STATUS'; - - static DOC_URL = 'http://doc.jetlinks.cn'; - - static BASE_CURD_MODAL_VISIBLE = 'BASE_CURD_MODAL_VISIBLE'; - - static BASE_CURD_CURRENT = 'BASE_CURD_CURRENT'; - - static BASE_CURD_MODEL = 'BASE_CURD_MODEL'; - - static BASE_UPDATE_DATA = 'BASE_UPDATE_DATA'; - - static GLOBAL_WEBSOCKET = 'GLOBAL-WEBSOCKET'; - - static BIND_USER_STATE = 'false'; - - static REFRESH_METADATA = 'refresh_metadata'; - - static REFRESH_METADATA_TABLE = 'refresh_metadata_table'; - - static GET_METADATA = 'get_metadata'; - - static REFRESH_DEVICE = 'refresh_device'; - - static AMAP_KEY = 'amap_key'; - - static Version_Code = 'version_code'; +export const SystemConst = { + API_BASE: 'api', + SYSTEM_NAME: 'Jetlinks', + LOGIN: 'LOGIN-STATUS', + DOC_URL: 'http://doc.jetlinks.cn', + BASE_CURD_MODAL_VISIBLE: 'BASE_CURD_MODAL_VISIBLE', + BASE_CURD_CURRENT: 'BASE_CURD_CURRENT', + BASE_CURD_MODEL: 'BASE_CURD_MODEL', + BASE_UPDATE_DATA: 'BASE_UPDATE_DATA', + GLOBAL_WEBSOCKET: 'GLOBAL-WEBSOCKET', + BIND_USER_STATE: 'false', + REFRESH_METADATA: 'refresh_metadata', + REFRESH_METADATA_TABLE: 'refresh_metadata_table', + GET_METADATA: 'get_metadata', + REFRESH_DEVICE: 'refresh_device', + AMAP_KEY: 'amap_key', + VERSION_CODE: 'version_code', } - -export default SystemConst; diff --git a/src/views/demo/table/index.vue b/src/views/demo/table/index.vue index 77d79fe3..5d305010 100644 --- a/src/views/demo/table/index.vue +++ b/src/views/demo/table/index.vue @@ -1,32 +1,7 @@ 新增 - + - {{slotProps.row.name}} + {{slotProps.name}} @@ -53,27 +35,41 @@ 直连设备 - - - 产品名称 - - 测试固定地址 - + - {{slotProps.row.id}} + {{slotProps.id}} - + - + - - + + @@ -86,38 +82,34 @@ import server from "@/utils/request"; import type { ActionsType } from '@/components/Table/index.vue' import { getImage } from '@/utils/comm'; +import { DeleteOutlined } from '@ant-design/icons-vue' const request = (data: any) => server.post(`/device-product/_query`, data) -const actions: ActionsType[] = [ + +const columns = [ { - key: 'edit', - // disabled: true, - text: "编辑", - tooltip: { - title: '编辑' - }, - icon: 'icon-rizhifuwu' + title: '名称', + dataIndex: 'name', + key: 'name', }, { - key: 'import', - // disabled: true, - text: "导入", - tooltip: { - title: '导入' - }, - icon: 'icon-xiazai' + title: 'ID', + dataIndex: 'id', + key: 'id', + scopedSlots: true }, { - key: 'delete', - // disabled: true, - text: "删除", - tooltip: { - title: '删除' - }, - popConfirm: { - title: '确认删除?' - }, - } + title: '分类', + dataIndex: 'classifiedName', + key: 'classifiedName', + }, + { + title: '操作', + key: 'action', + fixed: 'right', + width: 250, + scopedSlots: true + } ] const _selectedRowKeys = ref([]) @@ -141,6 +133,46 @@ const handleClick = (dt: any) => { } } +const getActions = (data: Partial>): ActionsType[] => { + if(!data){ + return [] + } + return [ + { + key: 'edit', + text: "编辑", + tooltip: { + title: '编辑' + }, + icon: 'icon-rizhifuwu' + }, + { + key: 'import', + text: "导入", + tooltip: { + title: '导入' + }, + icon: 'icon-xiazai' + }, + { + key: 'delete', + // disabled: true, + text: "删除", + disabled: !!data?.state, + tooltip: { + title: !!data?.state ? '正常的产品不能删除' : '删除' + }, + // popConfirm: { + // title: '确认删除?' + // }, + + icon: 'icon-huishouzhan' + } + ] +} + +const p = h('p', 'hi') + diff --git a/src/views/home/components/StepCard.vue b/src/views/home/components/StepCard.vue index a2c3883f..ccc87a5d 100644 --- a/src/views/home/components/StepCard.vue +++ b/src/views/home/components/StepCard.vue @@ -25,6 +25,10 @@ :open-number="openAccess" @confirm="againJumpPage" /> + @@ -35,6 +39,7 @@ import { QuestionCircleOutlined } from '@ant-design/icons-vue'; import { message } from 'ant-design-vue'; import AccessMethodDialog from './dialogs/AccessMethodDialog.vue'; +import FuncTestDialog from './dialogs/FuncTestDialog.vue'; import { recommendList } from '../index'; diff --git a/src/views/home/components/dialogs/FuncTestDialog.vue b/src/views/home/components/dialogs/FuncTestDialog.vue index dc7f1b62..9f285087 100644 --- a/src/views/home/components/dialogs/FuncTestDialog.vue +++ b/src/views/home/components/dialogs/FuncTestDialog.vue @@ -3,7 +3,7 @@ - + - + 搜索 - + 重置 - - - + - 取消 - 确认 + 取消 + 确认 @@ -65,10 +62,10 @@ const handleOk = () => { watch( () => props.openNumber, () => { + visible.value = true; clickReset(); getOptions(); clickSearch(); - visible.value = true; }, ); @@ -122,4 +119,11 @@ const selectItem: deviceInfo | {} = {}; const getList = () => {}; - + diff --git a/src/views/home/index.vue b/src/views/home/index.vue index b0229d95..7129a745 100644 --- a/src/views/home/index.vue +++ b/src/views/home/index.vue @@ -6,8 +6,7 @@ - - + @@ -17,7 +16,6 @@ import InitHome from './components/InitHome/index.vue'; import DeviceHome from './components/DeviceHome/index.vue'; import DevOpsHome from './components/DevOpsHome/index.vue'; import ComprehensiveHome from './components/ComprehensiveHome/index.vue'; -import ApiPage from '@/views/system/apiPage/index.vue' diff --git a/src/views/init-home/data/interface.ts b/src/views/init-home/data/interface.ts index 54f7b41f..adb411c4 100644 --- a/src/views/init-home/data/interface.ts +++ b/src/views/init-home/data/interface.ts @@ -6,7 +6,7 @@ export interface modalState { port: string; // 本地端口 publicHost: string; // 公网地址 publicPort: number | null; // 公网端口 - rules: Record; + } /**基本信息表单 */ @@ -21,17 +21,26 @@ export interface formState { } /** - * logo上传表单 + * 图片上传表单 */ export interface logoState { logoValue: string; logoLoading: boolean; + backLoading: boolean; + iconLoading: boolean; inLogo: boolean; inIcon: boolean; inBackground: boolean; iconValue: string; backValue: string; + backSize: number; + logoSize: number; + 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/init-home/index.vue b/src/views/init-home/index.vue index 56f0819a..865b088a 100644 --- a/src/views/init-home/index.vue +++ b/src/views/init-home/index.vue @@ -109,16 +109,44 @@ class="upload-image-border-logo" > + + + @@ -223,10 +250,45 @@ - + + + + - - - + + + + - - - + + + + + --> @@ -499,7 +566,7 @@ width="52vw" :maskClosable="false" @cancel="cancel" - @ok="handleOk" + @ok="saveCurrentData" okText="确定" cancelText="取消" class="modal-style" @@ -516,10 +583,10 @@ @@ -543,7 +610,7 @@ @@ -570,7 +637,7 @@ @@ -597,9 +664,23 @@ + > + {{ + item.label + }} + @@ -640,6 +721,7 @@ type="primary" class="btn-style" @click="submitData" + :loading="isSucessBasic || isSucessInit || isSucessRole" >确定 @@ -654,19 +736,38 @@ import { ExclamationCircleOutlined, LoadingOutlined, } from '@ant-design/icons-vue'; -import { ROLEKEYS, RoleData } from './data/RoleData'; +import RoleMenuData, { ROLEKEYS, RoleData } from './data/RoleData'; import type { Rule } from 'ant-design-vue/es/form'; import type { FormInstance, UploadChangeParam, UploadProps, + Form, } from 'ant-design-vue'; import { modalState, formState, logoState } from './data/interface'; import BaseMenu from './data/baseMenu'; -import { getSystemPermission, save } from '@/api/initHome'; +import { + getSystemPermission, + save, + addRole, + getRoleMenu, + updateRoleMenu, + getResourcesCurrent, + saveNetwork, + saveProtocol, + getProtocol, + saveAccessConfig, + saveProduct, + saveDevice, + changeDeploy, + deployDevice, +} from '@/api/initHome'; +import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable'; +import { LocalStore } from '@/utils/comm'; +import { message } from 'ant-design-vue'; const formRef = ref(); const menuRef = ref(); -const formBasicRef = ref(); +const formBasicRef = ref(); /** * 表单数据 */ @@ -710,7 +811,7 @@ const validateUrl = async (_rule: Rule, value: string) => { return Promise.reject('请输入公网地址'); } else { var reg = new RegExp( - /^(((?!(127\\.0\\.0\\.1)|(localhost)|(10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})|(172\\.((1[6-9])|(2\\d)|(3[01]))\\.\\d{1,3}\\.\\d{1,3})|(192\\.168\\.\\d{1,3}\\.\\d{1,3})).)*)(?:(?:\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])$/, + /^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$/, ); if (!reg.test(value)) { return Promise.reject('请输入正确的公网地址'); @@ -731,44 +832,44 @@ const validateNumber = async (_rule: Rule, value: string) => { return Promise.resolve(); } }; + /** * 初始化弹窗表单数据 */ -const ModalForm = reactive({ +const modalForm = reactive({ host: '0.0.0.0', port: '', publicHost: '', publicPort: null, - rules: { - host: [ - { - required: true, - message: '请选择本地地址', - }, - ], - port: [ - { - required: true, - message: '请选择本地端口', - }, - ], - publicHost: [ - { - required: true, - validator: validateUrl, - trigger: 'change', - }, - ], - publicPort: [ - { - required: true, - validator: validateNumber, - trigger: 'change', - }, - ], - }, }); -const { rules } = toRefs(ModalForm); +const rulesModle = ref({ + host: [ + { + required: true, + message: '请选择本地地址', + }, + ], + port: [ + { + required: true, + message: '请选择本地端口', + }, + ], + publicHost: [ + { + required: true, + validator: validateUrl, + trigger: 'change', + }, + ], + publicPort: [ + { + required: true, + validator: validateNumber, + trigger: 'change', + }, + ], +}); /** * 默认打开第一个初始菜单 @@ -777,6 +878,8 @@ const activeKey = ref('1'); const spinning = ref(false); const visible = ref(false); const flag = ref(false); +const action = ref(`${BASE_API_PATH}/file/static`); +const headers = ref({ [TOKEN_KEY]: LocalStore.get(TOKEN_KEY) }); /** * 角色勾选数据 */ @@ -797,13 +900,6 @@ const showModal = () => { } }; -/** - * 提交初始数据表单 - */ -const handleOk = async () => { - const valid = await formRef.value.validate(); -}; - /** * 表单取消事件 */ @@ -817,13 +913,34 @@ const cancel = () => { const logoData = reactive({ logoValue: '/public/logo.png', logoLoading: false, + backLoading: false, + iconLoading: false, inLogo: false, inIcon: false, inBackground: false, + backSize: 4, + logoSize: 1, iconValue: '/public/favicon.ico', backValue: '/public/images/login.png', + imageTypes: ['image/jpeg', 'image/png'], + iconTypes: ['image/x-icon'], /** - * 图片上传改变事件 + * logo格式校验 + */ + beforeLogoUpload: (file) => { + const isType = logoData.imageTypes.includes(file.type); + if (!isType) { + message.error(`请上传.jpg.png.jfif.pjp.pjpeg.jpeg格式的图片`); + return false; + } + const isSize = file.size / 1024 / 1024 < 4; + if (!isSize) { + message.error(`图片大小必须小于${4}M`); + } + return isType && isSize; + }, + /* + * logo上传改变事件 */ handleChangeLogo: (info) => { if (info.file.status === 'uploading') { @@ -838,33 +955,216 @@ const logoData = reactive({ /** * 背景图片上传之前 */ - beforeBackUpload: (file) => {}, + beforeBackUpload: (file) => { + const isType = logoData.imageTypes.includes(file.type); + if (!isType) { + message.error(`请上传.jpg.png.jfif.pjp.pjpeg.jpeg格式的图片`); + return false; + } + const isSize = file.size / 1024 / 1024 < 4; + if (!isSize) { + message.error(`图片大小必须小于${4}M`); + } + return isType && isSize; + }, /** * 背景图片发生改变 */ - changeBackUpload: (info) => {}, + changeBackUpload: (info) => { + if (info.file.status === 'uploading') { + logoData.backLoading = true; + } + if (info.file.status === 'done') { + info.file.url = info.file.response?.result; + logoData.backLoading = false; + logoData.backValue = info.file.response?.result; + } + }, + /** + * 上传之前 + */ + beforeIconUpload: (file) => { + const isType = logoData.iconTypes.includes(file.type); + if (!isType) { + message.error(`请上传ico格式的图片`); + return false; + } + const isSize = file.size / 1024 / 1024 < 1; + if (!isSize) { + message.error(`图片大小必须小于${1}M`); + } + return isType && isSize; + }, + /** + * 图标发生改变 + */ + changeIconUpload: (info) => { + if (info.file.status === 'uploading') { + logoData.iconLoading = true; + } + if (info.file.status === 'done') { + info.file.url = info.file.response?.result; + logoData.iconLoading = true; + logoData.iconValue = info.file.response?.result; + } + }, }); const { logoValue, logoLoading, + iconLoading, + backLoading, inLogo, iconValue, inIcon, inBackground, backValue, handleChangeLogo, + beforeBackUpload, + changeBackUpload, + beforeLogoUpload, + beforeIconUpload, + changeIconUpload, + imageTypes, + iconTypes, } = toRefs(logoData); /** * 提交基础表单 */ const basicData = reactive({ + isSucessBasic: false, /** * 提交基础表单数据 */ - saveBasicInfo: async () => {}, + saveBasicInfo: async () => { + // return new Promise(async (resolve) => { + const vaild = await formBasicRef.value.validate(); + console.log(vaild, 'vaild '); + if (vaild) { + const item = [ + { + scope: 'front', + properties: { + ...form, + apikey: '', + 'base-path': '', + }, + }, + { + scope: 'amap', + properties: { + api: form.apikey, + }, + }, + { + scope: 'paths', + properties: { + 'base-path': form.basePath, + }, + }, + ]; + const res = await save(item); + if (res.status === 200) { + const ico: any = document.querySelector('link[rel="icon"]'); + if (ico !== null) { + ico.href = form.icon; + } + } else { + basicData.isSucessBasic = true; + } + } else { + basicData.isSucessBasic = true; + } + // }); + }, }); +/** + * 提交角色数据 + */ +const roleData = reactive({ + isSucessRole: false, + /** + * 根据菜单找角色 + */ + findMenuByRole: (menu: any[], code: string): any => { + let _item = null; + menu.some((item) => { + if (item.code === code) { + _item = item; + return true; + } + + if (item.children) { + const childrenItem = roleData.findMenuByRole( + item.children, + code, + ); + if (childrenItem) { + _item = childrenItem; + return true; + } + return false; + } + + return null; + }); + return _item; + }, + /** + * 保存角色 + */ + addRoleData: async () => { + return new Promise((resolve) => { + if (!keys.value.length) { + return resolve(true); + } + let Count = 0; + keys.value.forEach(async (item, index) => { + const _itemData = RoleData[item]; + // 添加该角色 + const res = await addRole(_itemData); + if (res.status === 200) { + const menuTree = await getRoleMenu(res.result.id); + if (menuTree.status === 200) { + const _roleData = (RoleMenuData[item] as []).filter( + (roleItem: any) => { + const _menu = roleData.findMenuByRole( + menuTree.result, + roleItem.code, + ); + if (_menu) { + roleItem.id = _menu.id; + roleItem.parentId = _menu.parentId; + roleItem.createTime = _menu.createTime; + return true; + } + return false; + }, + ); + //更新权限 + const roleRes = await updateRoleMenu(res.result.id, { + menus: _roleData, + }); + if (roleRes.status === 200) { + Count += 1; + } + if (index === keys.value.length - 1) { + resolve(Count === keys.value.length); + } + } else if (index === keys.value.length - 1) { + resolve(Count === keys.value.length); + } + } else if (index === keys.value.length - 1) { + resolve(Count === keys.value.length); + roleData.isSucessRole = true; + } + }); + }); + }, +}); +const { isSucessRole } = toRefs(roleData); /** * 获取菜单数据 */ @@ -882,7 +1182,6 @@ const menuDatas = reactive({ ); const _count = menuDatas.menuCount(newTree); menuDatas.count = _count; - console.log(menuDatas.count, 'menuDatas.count'); } }, /** @@ -918,16 +1217,118 @@ const menuDatas = reactive({ }, 0); }, }); - const { count } = toRefs(menuDatas); +/** + * 提交初始化数据 + */ +const initialization = reactive({ + isSucessInit: false, + optionPorts: [], + /** + * 查询端口数据 + */ + getCurrentPort: async () => { + const resp = await getResourcesCurrent(); + const current = resp?.result; + const _host = + current.find((item: any) => item.host === '0.0.0.0')?.ports[ + 'TCP' + ] || []; + initialization.optionPorts = _host?.map((p: any) => ({ + label: p, + value: p, + })); + }, + /** + * 提交初始数据表单 + */ + + saveCurrentData: async () => { + // return new Promise(async (resolve) => { + const valid = await formRef.value.validate(); + console.log(valid, 'valid'); + // if (valid) { + // try { + // // 新增网络组件 + // const network = await saveNetwork({ + // type: 'MQTT_SERVER', + // shareCluster: true, + // name: 'MQTT网络组件', + // configuration: { + // host: '0.0.0.0', + // secure: false, + // port: modalForm.port, + // publicHost: modalForm.publicHost, + // publicPort: modalForm.publicPort, + // }, + // }); + // // 保存协议 + // const protocol = await saveProtocol(); + // let protocolItem: any = undefined; + // if (protocol.status === 200) { + // const proid = await getProtocol(); + // if (proid.status === 200) { + // protocolItem = (proid?.result || []).find( + // (it: any) => it.name === 'JetLinks官方协议', + // ); + // } + // } + // // 新增设备接入网关 + // const accessConfig = await saveAccessConfig({ + // name: 'MQTT类型设备接入网关', + // provider: 'mqtt-server-gateway', + // protocol: protocolItem?.id, + // transport: 'MQTT', + // channel: 'network', + // channelId: network?.result?.id, + // }); + // // 新增产品 + // const product = await saveProduct({ + // name: 'MQTT产品', + // messageProtocol: protocolItem?.id, + // protocolName: protocolItem?.name, + // transportProtocol: 'MQTT', + // deviceType: 'device', + // accessId: accessConfig.result?.id, + // accessName: accessConfig.result?.name, + // accessProvider: 'mqtt-server-gateway', + // }); + // // 新增设备 + // const device = await saveDevice({ + // name: 'MQTT设备', + // productId: product?.result?.id, + // productName: product?.result?.name, + // }); + // if (device.status === 200) { + // changeDeploy(product.result.id); + // deployDevice(device.result.id); + // } + + // flag.value = true; + // visible.value = false; + // } catch (e) { + // initialization.isSucessInit = true; + // // resolve(false); + // } + // } + initialization.isSucessInit = true; + }, +}); +const { optionPorts, saveCurrentData, isSucessInit } = toRefs(initialization); + /** * 初始化 */ menuDatas.getSystemPermissionData(); +initialization.getCurrentPort(); /** * 提交所有数据 */ -const submit = () => {}; +const submitData = () => { + initialization.saveCurrentData(); + roleData.addRoleData(); + basicData.saveBasicInfo(); +}; diff --git a/src/views/iot-card/components/Guide.vue b/src/views/iot-card/components/Guide.vue new file mode 100644 index 00000000..52b86134 --- /dev/null +++ b/src/views/iot-card/components/Guide.vue @@ -0,0 +1,55 @@ + + + {{ title }} + + + + {{ english }} + + + + + + diff --git a/src/views/system/apiPage/components/ApiDoes.vue b/src/views/system/apiPage/components/ApiDoes.vue index a894042d..85dfc5e0 100644 --- a/src/views/system/apiPage/components/ApiDoes.vue +++ b/src/views/system/apiPage/components/ApiDoes.vue @@ -8,27 +8,27 @@ - + 请求数据类型 - {{ + {{ getContent(selectApi.requestBody) || 'application/x-www-form-urlencoded' }} 响应数据类型 - {{ `["/"]` }} + {{ `["/"]` }} 请求参数 - {{ slotProps.row.required + '' }} + {{ Boolean(slotProps.row.required) + '' }} {{ slotProps.row.schema.type }} @@ -36,6 +36,39 @@ + + 响应状态 + + + + + + + + + + + + 响应参数 + + + + + @@ -49,11 +82,21 @@ const props = defineProps({ type: Object as PropType, required: true, }, + schemas: { + type: Object, + required: true, + }, }); const { selectApi } = toRefs(props); -const columns = { - request: [ +type tableCardType = { + columns: object[]; + tableData: object[]; + activeKey?: any; + getData?: any; +}; +const requestCard = reactive({ + columns: [ { title: '参数名', dataIndex: 'name', @@ -82,13 +125,136 @@ const columns = { scopedSlots: true, }, ], -}; + tableData: [], + getData: () => { + requestCard.tableData = props.selectApi.parameters; + }, +}); +const responseStatusCard = reactive({ + activeKey: '', + columns: [ + { + title: '状态码', + dataIndex: 'code', + key: 'code', + }, + { + title: '说明', + dataIndex: 'desc', + key: 'desc', + }, + { + title: 'schema', + dataIndex: 'schema', + key: 'schema', + }, + ], + tableData: [], + getData: () => { + if (!Object.keys(props.selectApi.responses).length) + return (responseStatusCard.tableData = []); + + const tableData = []; + Object.entries(props.selectApi.responses || {}).forEach((item: any) => { + const desc = item[1].description; + const schema = item[1].content['*/*'].schema.$ref?.split('/') || ''; + + tableData.push({ + code: item[0], + desc, + schema: schema && schema.pop(), + }); + }); + responseStatusCard.activeKey = tableData[0]?.code; + responseStatusCard.tableData = tableData; + }, +}); +const tabs = computed(() => + responseStatusCard.tableData + .map((item: any) => item.code + '') + .filter((code: string) => code !== '400'), +); +const respParamsCard = reactive({ + columns: [ + { + title: '参数名称', + dataIndex: 'paramsName', + }, + { + title: '参数说明', + dataIndex: 'desc', + }, + { + title: '类型', + dataIndex: 'paramsType', + }, + ], + tableData: [], + getData: (code: string) => { + type schemaObjType = { + paramsName: string; + paramsType: string; + desc: string; + children?: schemaObjType[]; + }; + + const schemaName = responseStatusCard.tableData.find( + (item: any) => item.code === code, + ).schema; + const schemas = toRaw(props.schemas); + function findData(schemaName: string) { + if (!schemaName || !schemas[schemaName]) { + return []; + } + const result: schemaObjType[] = []; + const schema = schemas[schemaName]; + const basicType = ['string', 'integer', 'boolean']; + Object.entries(schema.properties).forEach((item: [string, any]) => { + const paramsType = + item[1].type || + (item[1].$ref && item[1].$ref.split('/').pop()) || + (item[1].items && item[1].items.$ref.split('/').pop()) || + ''; + const schemaObj: schemaObjType = { + paramsName: item[0], + paramsType, + desc: item[1].description || '', + }; + if (!basicType.includes(paramsType)) + schemaObj.children = findData(paramsType); + result.push(schemaObj); + }); + console.log(result); + + return result; + } + + respParamsCard.tableData = findData(schemaName); + // console.log(respParamsCard.tableData); + }, +}); -console.log(selectApi.value); const getContent = (data: any) => { - if (!data) return ''; - return Object.keys(data.content)[0]; + if (data && data.content) { + return Object.keys(data.content || {})[0]; + } + return ''; }; +onMounted(() => { + requestCard.getData(); + responseStatusCard.getData(); +}); +watch( + () => props.selectApi, + () => { + requestCard.getData(); + responseStatusCard.getData(); + }, +); + +watch([() => responseStatusCard.activeKey, () => props.selectApi], (n) => { + n[0] && respParamsCard.getData(n[0]); +}); \ No newline at end of file + diff --git a/src/views/system/apiPage/components/ChooseApi.vue b/src/views/system/apiPage/components/ChooseApi.vue index 24362336..bb6e5548 100644 --- a/src/views/system/apiPage/components/ChooseApi.vue +++ b/src/views/system/apiPage/components/ChooseApi.vue @@ -43,9 +43,9 @@ const columns = [ }, ]; const rowSelection: TableProps['rowSelection'] = { - // onChange: (selectedRowKeys, selectedRows) => { - // console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows); - // }, + onChange: (selectedRowKeys, selectedRows) => { + console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows); + }, }; const jump = (row:object) => { diff --git a/src/views/system/apiPage/components/LeftTree.vue b/src/views/system/apiPage/components/LeftTree.vue index cbae4bea..e933c925 100644 --- a/src/views/system/apiPage/components/LeftTree.vue +++ b/src/views/system/apiPage/components/LeftTree.vue @@ -32,13 +32,14 @@ const getTreeData = () => { Promise.all(allPromise).then((values) => { values.forEach((item: any, i) => { tree[i].children = combData(item?.paths); + tree[i].schemas = item.components.schemas }); treeData.value = tree; }); }); }; const clickSelectItem: TreeProps['onSelect'] = (key, node: any) => { - emits('select', node.node.dataRef); + emits('select', node.node.dataRef, node.node?.parent.node.schemas); }; onMounted(() => { diff --git a/src/views/system/apiPage/index.d.ts b/src/views/system/apiPage/index.d.ts index e206edd0..7937398b 100644 --- a/src/views/system/apiPage/index.d.ts +++ b/src/views/system/apiPage/index.d.ts @@ -1,9 +1,11 @@ export type treeNodeTpye = { name: string; key: string; + schemas?:object; link?: string; apiList?: object[]; children?: treeNodeTpye[]; + }; export type methodType = { [key: string]: object @@ -17,6 +19,7 @@ export type apiDetailsType = { url: string; method: string; summary: string; - parameters: []; + parameters: any[]; requestBody?: any; + responses:object; } \ No newline at end of file diff --git a/src/views/system/apiPage/index.vue b/src/views/system/apiPage/index.vue index cb82abca..397806a5 100644 --- a/src/views/system/apiPage/index.vue +++ b/src/views/system/apiPage/index.vue @@ -15,12 +15,12 @@ class="api-details" v-show="selectedApi.url && tableData.length > 0" > - 返回 - + @@ -40,7 +40,8 @@ import ApiDoes from './components/ApiDoes.vue'; import ApiTest from './components/ApiTest.vue'; const tableData = ref([]); -const treeSelect = (node: treeNodeTpye) => { +const treeSelect = (node: treeNodeTpye, nodeSchemas:object = {}) => { + schemas.value = nodeSchemas if (!node.apiList) return; const apiList: apiObjType[] = node.apiList as apiObjType[]; const table: any = []; @@ -61,10 +62,14 @@ const treeSelect = (node: treeNodeTpye) => { }; const activeKey = ref('does'); -const initSelectedApi = { +const schemas = ref({}); +const initSelectedApi:apiDetailsType = { url: '', method: '', summary: '', + parameters: [], + responses: {}, + requestBody: {} }; const selectedApi = ref(initSelectedApi);
+
请求数据类型 - {{ + {{ getContent(selectApi.requestBody) || 'application/x-www-form-urlencoded' }} 响应数据类型 - {{ `["/"]` }} + {{ `["/"]` }}