fix: 初始化页面

This commit is contained in:
leiqiaochu 2023-02-21 16:45:36 +08:00
parent aab1fc7d45
commit e2eabb7bcb
8 changed files with 721 additions and 962 deletions

BIN
public/images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -31,7 +31,7 @@ export const getSystemPermission = () =>server.get(`/system/resources/permission
export const saveNetwork = (data: any) => server.post(`/network/config`, data) export const saveNetwork = (data: any) => server.post(`/network/config`, data)
// 保存协议 // 保存协议
export const saveProtocol = () => server.post(`/protocol/default-protocol/_save`,) export const saveProtocol = () => server.post(`/protocol/default-protocol/_save`)
// 新增设备接入网关 // 新增设备接入网关
export const saveAccessConfig = (data: any) => server.post(`/gateway/device`, data) export const saveAccessConfig = (data: any) => server.post(`/gateway/device`, data)

View File

@ -83,15 +83,15 @@
</div> </div>
<div <div
class="upload-image" class="upload-image"
v-if="logoValue" v-if="form.logo"
:style=" :style="
logoValue form.logo
? `background-image: url(${logoValue});` ? `background-image: url(${form.logo});`
: '' : ''
" "
></div> ></div>
<div <div
v-if="logoValue" v-if="form.logo"
class="upload-image-mask" class="upload-image-mask"
> >
点击修改 点击修改
@ -162,15 +162,15 @@
</div> </div>
<div <div
class="upload-image-icon" class="upload-image-icon"
v-if="iconValue" v-if="form.ico"
:style=" :style="
iconValue form.ico
? `background-image: url(${iconValue});` ? `background-image: url(${form.ico});`
: '' : ''
" "
></div> ></div>
<div <div
v-if="iconValue" v-if="form.ico"
class="upload-image-mask" class="upload-image-mask"
> >
点击修改 点击修改
@ -221,15 +221,15 @@
</div> </div>
<div <div
class="upload-image" class="upload-image"
v-if="backValue" v-if="form.background"
:style=" :style="
backValue form.background
? `background-image: url(${backValue});` ? `background-image: url(${form.background});`
: '' : ''
" "
></div> ></div>
<div <div
v-if="backValue" v-if="form.background"
class="upload-image-mask" class="upload-image-mask"
> >
点击修改 点击修改
@ -256,6 +256,7 @@
import { modalState, formState, logoState } from '../data/interface'; import { modalState, formState, logoState } from '../data/interface';
import { getImage } from '@/utils/comm.ts'; import { getImage } from '@/utils/comm.ts';
import { Form } from 'ant-design-vue'; import { Form } from 'ant-design-vue';
import { FILE_UPLOAD } from '@/api/comm';
import { import {
getSystemPermission, getSystemPermission,
save, save,
@ -275,18 +276,18 @@ import {
} from '@/api/initHome'; } from '@/api/initHome';
import { ValidateErrorEntity } from 'ant-design-vue/es/form/interface'; import { ValidateErrorEntity } from 'ant-design-vue/es/form/interface';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { LocalStore } from '@/utils/comm';
import { TOKEN_KEY } from '@/utils/variable';
const formRef = ref(); const formRef = ref();
const menuRef = ref(); const menuRef = ref();
const formBasicRef = ref(); const formBasicRef = ref();
const useForm = Form.useForm; const useForm = Form.useForm;
const iconValue = ref('/public/favicon.ico');
const backValue = ref('/public/images/login.png');
const logoValue = ref('/public/logo.png');
const logoLoading = ref(false); const logoLoading = ref(false);
const backLoading = ref(false); const backLoading = ref(false);
const iconLoading = ref(false); const iconLoading = ref(false);
const imageTypes = ref(['image/jpeg', 'image/png']); const imageTypes = ref(['image/jpeg', 'image/png']);
const iconTypes = ref(['image/x-icon']); const iconTypes = ref(['image/x-icon']);
const headers = ref({ 'X-Access-Token': LocalStore.get(TOKEN_KEY) });
/** /**
* 表单数据 * 表单数据
*/ */
@ -295,17 +296,11 @@ const form = ref<formState>({
headerTheme: 'light', headerTheme: 'light',
apikey: '', apikey: '',
basePath: `${window.location.origin}/api`, basePath: `${window.location.origin}/api`,
logo: '', logo: getImage('/logo.png'),
icon: '', ico: getImage('/favicon.ico'),
background:getImage('/login.png')
}); });
const rulesFrom = ref({ const rulesFrom = ref({
title: [
{
required: true,
message: '请输入系统名称',
trigger: 'change',
},
],
headerTheme: [ headerTheme: [
{ {
required: true, required: true,
@ -328,14 +323,15 @@ const { resetFields, validate, validateInfos } = useForm(
/** /**
* 提交数据 * 提交数据
*/ */
const saveBasicInfo = async () => { const saveBasicInfo = () =>{
return new Promise( async (resolve) => {
validate() validate()
.then(async () => { .then(async () => {
const item = [ const item = [
{ {
scope: 'front', scope: 'front',
properties: { properties: {
...form, ...form.value,
apikey: '', apikey: '',
'base-path': '', 'base-path': '',
}, },
@ -343,32 +339,32 @@ const saveBasicInfo = async () => {
{ {
scope: 'amap', scope: 'amap',
properties: { properties: {
api: form.apikey, api: form.value.apikey,
}, },
}, },
{ {
scope: 'paths', scope: 'paths',
properties: { properties: {
'base-path': form.basePath, 'base-path': form.value.basePath,
}, },
}, },
]; ];
const res = await save(item); const res = await save(item);
if (res.status === 200) { if (res.status === 200) {
resolve(true);
const ico: any = document.querySelector('link[rel="icon"]'); const ico: any = document.querySelector('link[rel="icon"]');
if (ico !== null) { if (ico !== null) {
ico.href = form.icon; ico.href = form.value.ico;
} }
}else {
resolve(false);
} }
// basicData.isSucessBasic = 3;
// } else {
// basicData.isSucessBasic = 2;
// }
}) })
.catch((error: ValidateErrorEntity<formState>) => { .catch((error: ValidateErrorEntity<formState>) => {
// basicData.isSucessBasic = 2; resolve(false);
}); });
}; })
}
/** /**
* logo格式校验 * logo格式校验
*/ */
@ -395,14 +391,41 @@ const handleChangeLogo = (info: any) => {
if (info.file.status === 'done') { if (info.file.status === 'done') {
info.file.url = info.file.response?.result; info.file.url = info.file.response?.result;
logoLoading.value = false; logoLoading.value = false;
logoValue.value = info.file.response?.result; form.value.logo = info.file.response?.result;
} }
}; };
/**
* 浏览器页签上传之前
*/
const beforeIconUpload = (file:any) => {
const isType = iconTypes.value.includes(file.type);
if(!isType){
message.error('请上传ico格式的图片');
return false;
}
const isSize = file.size / 1024 / 1024 < 1;
if(!isSize){
message.error('支持1M以内的图片');
}
return isType && isSize;
}
/**
* 浏览器页签发生改变
*/
const changeIconUpload = (info: any) => {
if (info.file.status === 'uploading') {
iconLoading.value = true;
}
if (info.file.status === 'done') {
info.file.url = info.file.response?.result;
iconLoading.value = false;
form.value.ico = info.file.response?.result;
}
}
/** /**
* 背景图片上传之前 * 背景图片上传之前
*/ */
const beforeBackUpload = (file: any) => { const beforeBackUpload = (file: any) => {
const isType = imageTypes.value.includes(file.type); const isType = imageTypes.value.includes(file.type);
if (!isType) { if (!isType) {
message.error(`请上传.jpg.png.jfif.pjp.pjpeg.jpeg格式的图片`); message.error(`请上传.jpg.png.jfif.pjp.pjpeg.jpeg格式的图片`);
@ -424,7 +447,7 @@ const changeBackUpload = (info: any) => {
if (info.file.status === 'done') { if (info.file.status === 'done') {
info.file.url = info.file.response?.result; info.file.url = info.file.response?.result;
backLoading.value = false; backLoading.value = false;
backValue.value = info.file.response?.result; form.value.background = info.file.response?.result;
} }
}; };
defineExpose({ defineExpose({

View File

@ -0,0 +1,349 @@
<template>
<div>
<img
class="init-data-img"
@click="showModal"
:src="
flag
? getImage('/init-home/data-enabled.png')
: getImage('/init-home/data-disabled.png')
"
/>
</div>
<!-- 初始数据提交表单 -->
<a-modal
v-model:visible="visible"
title="初始数据"
width="52vw"
:maskClosable="false"
@cancel="cancel"
@ok="save"
okText="确定"
cancelText="取消"
class="modal-style"
v-bind="layout"
>
<div class="data-content">
<p class="data-p-style">
<ExclamationCircleOutlined style="margin: 0 0 0 5px" />
初始化数据包括MQTT产品MQTT设备MQTT类型设备接入网关MQTT网络组件Jetlinks
官方协议
</p>
</div>
<div style="margin-top: 20px">
<a-form
layout="vertical"
:model="modalForm"
ref="formRef"
:rules="rulesModle"
>
<a-row :span="24" :gutter="24">
<a-col :span="12">
<a-form-item name="host">
<template #label>
<span>本地地址 </span>
<a-tooltip
title="绑定到服务器上的网卡地址,绑定到所有网卡:0.0.0.0"
>
<img
class="img-style"
:src="getImage('/init-home/mark.png')"
/>
</a-tooltip>
</template>
<a-input
v-model:value="modalForm.host"
:disabled="true"
/>
</a-form-item>
<a-form-item name="publicHost">
<template #label>
<span>公网地址 </span>
<a-tooltip
title="对外提供访问的地址内网环境时填写服务器的内网IP地址"
>
<img
class="img-style"
:src="getImage('/init-home/mark.png')"
/>
</a-tooltip>
</template>
<a-input v-model:value="modalForm.publicHost">
</a-input>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item name="port">
<template #label>
<span>本地端口 </span>
<a-tooltip title="监听指定端口的请求">
<img
class="img-style"
:src="getImage('/init-home/mark.png')"
/>
</a-tooltip>
</template>
<a-select v-model:value="modalForm.port">
<a-select-option
v-for="item in optionPorts"
:key="item"
:value="item.value"
:label="item.label"
>{{ item.label }}</a-select-option
>
</a-select>
</a-form-item>
<a-form-item name="publicPort">
<template #label>
<span>公网端口 </span>
<a-tooltip title="对外提供访问的端口">
<img
class="img-style"
:src="getImage('/init-home/mark.png')"
/>
</a-tooltip>
</template>
<a-input-number
v-model:value="modalForm.publicPort"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { getImage } from '@/utils/comm';
import {
saveNetwork,
getResourcesCurrent,
saveProtocol,
getProtocol,
saveAccessConfig,
saveProduct,
saveDevice,
changeDeploy,
deployDevice,
} from '@/api/initHome';
import { modalState } from '../data/interface';
import { ValidateErrorEntity } from 'ant-design-vue/es/form/interface';
import type { Rule } from 'ant-design-vue/es/form';
import { message } from 'ant-design-vue/es';
const formRef = ref();
/**
* 初始化数据状态
*/
const flag = ref<boolean>(false);
const visible = ref<boolean>(false);
/**
* 初始化弹窗表单数据
*/
const modalForm = reactive<modalState>({
host: '0.0.0.0',
port: '',
publicHost: '',
publicPort: null,
});
/**
* 校验官网地址
*/
const validateUrl = async (_rule: Rule, value: string) => {
if (!value) {
return Promise.reject('请输入公网地址');
} else {
var reg = new RegExp(
/^(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('请输入正确的公网地址');
}
return Promise.resolve();
}
};
/**
* 校验数字
*/
const validateNumber = async (_rule: Rule, value: string) => {
if (!value) {
return Promise.reject('请输入公网端口');
} else {
if (Number(value) < 1 || Number(value) > 65535) {
return Promise.reject('请输入1~65535的正整数');
}
return Promise.resolve();
}
};
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',
},
],
});
/**
* 初始数据弹窗点击事件
*/
const showModal = () => {
if (flag.value) {
flag.value = false;
} else {
visible.value = true;
}
};
/**
* 表单取消事件
*/
const cancel = () => {
formRef.value.resetFields();
};
/**
* 提交初始化数据
*/
const initialization = reactive({
isSucessInit: 0,
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,
}));
},
});
/**
* 提交初始数据表单
*/
const saveCurrentData = () => {
return new Promise(async (resolve) => {
if (!flag.value) {
return resolve(true);
}
formRef.value
.validate()
.then(async () => {
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) {
await changeDeploy(product.result.id);
await deployDevice(device.result.id);
}
resolve(device.status == 200);
} catch (e) {
resolve(false);
}
})
.catch((error: ValidateErrorEntity<modalState>) => {
resolve(false);
});
});
};
const { optionPorts, isSucessInit } = toRefs(initialization);
const save = () => {
message.success('保存成功');
flag.value = true;
visible.value = false;
};
/**
* 初始化
*/
initialization.getCurrentPort();
defineExpose({
save: saveCurrentData,
});
</script>
<style lang="less" scoped>
.init-data-img {
width: 300px;
}
.modal-style {
.data-content {
background: rgb(236, 237, 238);
.data-p-style {
padding: 10px;
}
}
}
</style>

View File

@ -0,0 +1,80 @@
<template>
<div class="menu-style">
<div class="menu-img">
<img :src="getImage('/init-home/menu.png')" />
</div>
<div class="menu-info">
<b>系统初始化{{ count }}个菜单</b>
<div>初始化后的菜单可在菜单管理页面进行维护管理</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { getImage } from '@/utils/comm';
import BaseMenu from '../data/baseMenu';
import { getSystemPermission } from '@/api/initHome'
/**
* 获取菜单数据
*/
const menuDatas = reactive({
count: 0,
/**
* 获取当前系统权限信息
*/
getSystemPermissionData: async () => {
const resp = await getSystemPermission();
if (resp.status === 200) {
const newTree = menuDatas.filterMenu(
resp.result.map((item: any) => JSON.parse(item).id),
BaseMenu,
);
const _count = menuDatas.menuCount(newTree);
menuDatas.count = _count;
}
},
/**
* 过滤菜单
*/
filterMenu: (permissions: string[], menus: any[]) => {
return menus.filter((item) => {
let isShow = false;
if (item.showPage && item.showPage.length) {
isShow = item.showPage.every((pItem: any) => {
return permissions.includes(pItem);
});
}
if (item.children) {
item.children = menuDatas.filterMenu(
permissions,
item.children,
);
}
return isShow || !!item.children?.length;
});
},
/**
* 计算菜单数量
*/
menuCount: (menus: any[]) => {
return menus.reduce((pre, next) => {
let _count = 1;
if (next.children) {
_count = menuDatas.menuCount(next.children);
}
return pre + _count;
}, 0);
},
});
const { count } = toRefs(menuDatas);
menuDatas.getSystemPermissionData();
</script>
<style lang="less" scoped>
.menu-style {
display: flex;
align-items: center;
.menu-img {
margin-right: 16px;
}
}
</style>

View File

@ -0,0 +1,204 @@
<template>
<div class="init-home-role">
<a-checkbox-group @change="getCheckValue">
<div class="init-home-role-content">
<div
class="role-item role-item-1"
:style="
keys.includes('device')
? 'background-color: #f5f5f5;'
: ''
"
>
<div class="role-item-title">
<a-checkbox :value="ROLEKEYS.device"></a-checkbox>
<div class="role-title">设备接入岗</div>
</div>
<div class="role-item-content"></div>
<div class="role-item-footer">
该角色负责设备接入模块的维护管理
</div>
</div>
<div
class="role-item role-item-2"
:style="
keys.includes('link')
? 'background-color: #f5f5f5;'
: ''
"
>
<div class="role-item-title">
<a-checkbox :value="ROLEKEYS.link"></a-checkbox>
<div class="role-title">运维管理岗</div>
</div>
<div class="role-item-content"></div>
<div class="role-item-footer">
该角色负责系统运维模块的维护管理
</div>
</div>
<div
class="role-item role-item-3"
:style="
keys.includes('complex')
? 'background-color: #f5f5f5;'
: ''
"
>
<div class="role-item-title">
<a-checkbox :value="ROLEKEYS.complex"></a-checkbox>
<div class="role-title">综合管理岗</div>
</div>
<div class="role-item-content"></div>
<div class="role-item-footer">
该角色负责系统运维和设备接入模块的维护管理
</div>
</div>
</div>
</a-checkbox-group>
</div>
</template>
<script lang="ts" setup>
import RoleMenuData, { ROLEKEYS, RoleData } from '../data/RoleData';
import { updateRoleMenu, addRole, getRoleMenu } from '@/api/initHome';
/**
* 角色勾选数据
*/
const keys = ref([]);
/**
* 获取角色选择数据
*/
const getCheckValue = (val: any) => {
keys.value = val;
};
/**
* 根据菜单找角色
*/
const 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 = findMenuByRole(item.children, code);
if (childrenItem) {
_item = childrenItem;
return true;
}
return false;
}
return null;
});
return _item;
};
/**
* 保存角色
*/
const 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 = 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 = 2;
}
});
});
};
defineExpose({
submitRole: addRoleData,
});
</script>
<style lang="less" scoped>
.init-home-role {
.init-home-role-content {
display: flex;
grid-gap: 24px;
gap: 24px;
}
.role-item-1 {
background-image: url(/images/init-home/role1.png);
}
.role-item-2 {
background-image: url(/images/init-home/role2.png);
}
.role-item-3 {
background-image: url(/images/init-home/role3.png);
}
.role-item {
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
margin-bottom: 30px;
padding: 24px;
background-repeat: no-repeat;
background-position: 50%;
background-size: 370px;
border: 1px solid #f5f5f5;
.role-item-title {
display: flex;
.role-title {
flex: 1 1 auto;
font-weight: 700;
font-size: 16px;
}
}
.role-item-content {
width: 250px;
height: 260px;
margin-top: 24px;
}
.role-item-footer {
position: absolute;
bottom: -30px;
left: 0;
width: 100%;
color: #999;
font-size: 12px;
text-align: center;
}
}
}
</style>

View File

@ -16,7 +16,8 @@ export interface formState {
apikey: string; // 高德 API key apikey: string; // 高德 API key
basePath: string; // 系统后台访问的URL basePath: string; // 系统后台访问的URL
logo: string; // 系统logo logo: string; // 系统logo
icon: string; // 浏览器页签 ico: string; // 浏览器页签
background:string; //登录背景
} }
/** /**

File diff suppressed because it is too large Load Diff