Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev
# Conflicts: # components.d.ts # src/utils/consts.ts
This commit is contained in:
commit
748fac80a7
|
@ -0,0 +1,28 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: ['@commitlint/config-conventional'],
|
||||||
|
rules: {
|
||||||
|
'type-enum': [
|
||||||
|
2,
|
||||||
|
'always',
|
||||||
|
[
|
||||||
|
'build', // 编译相关修改(新版本发布)
|
||||||
|
'feat', // 新功能
|
||||||
|
'fix', // 修复bug
|
||||||
|
'update', // 更新某功能
|
||||||
|
'refactor', // 重构
|
||||||
|
'docs', // 文档
|
||||||
|
'chore', // 增加依赖或库
|
||||||
|
'style', // 格式(不影响代码变动)
|
||||||
|
'revert', // 撤销commit 回滚上一版本
|
||||||
|
'perf', // 性能优化
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'type-case': [0],
|
||||||
|
'type-empty': [0],
|
||||||
|
'scope-empty': [0],
|
||||||
|
'scope-case': [0],
|
||||||
|
'subject-full-stop': [0, 'never'],
|
||||||
|
'subject-case': [0, 'never'],
|
||||||
|
'header-max-length': [0, 'always', 72]
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ node_modules
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
components.d.ts
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode
|
.vscode
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx --no -- commitlint --edit ${1}
|
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
|
@ -8,12 +8,18 @@
|
||||||
"build": "vite build --mode production",
|
"build": "vite build --mode production",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"eslint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
|
"eslint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
|
||||||
"prettier": "prettier --write"
|
"lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
|
||||||
|
"prettier": "prettier --write",
|
||||||
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
||||||
"@vuemap/vue-amap": "^1.1.20",
|
"@vuemap/vue-amap": "^1.1.20",
|
||||||
"ant-design-vue": "^3.2.15",
|
"ant-design-vue": "^3.2.15",
|
||||||
"axios": "^1.2.1",
|
"axios": "^1.2.1",
|
||||||
|
"echarts": "^5.4.1",
|
||||||
|
"jetlinks-store": "^0.0.3",
|
||||||
|
"js-cookie": "^3.0.1",
|
||||||
"less": "^4.1.3",
|
"less": "^4.1.3",
|
||||||
"less-loader": "^11.1.0",
|
"less-loader": "^11.1.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
@ -26,16 +32,32 @@
|
||||||
"vue-router": "^4.1.6"
|
"vue-router": "^4.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "^17.4.1",
|
||||||
|
"@commitlint/config-conventional": "^17.4.0",
|
||||||
"@types/lodash-es": "^4.17.6",
|
"@types/lodash-es": "^4.17.6",
|
||||||
"@types/moment": "^2.13.0",
|
"@types/moment": "^2.13.0",
|
||||||
"@types/node": "^18.11.17",
|
"@types/node": "^18.11.17",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
"@vuemap/unplugin-resolver": "^1.0.4",
|
"@vuemap/unplugin-resolver": "^1.0.4",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
|
"commitlint": "^17.4.1",
|
||||||
|
"husky": "^8.0.0",
|
||||||
|
"lint-staged": "^13.1.0",
|
||||||
|
"mrm": "^4.1.13",
|
||||||
"prettier": "^2.8.1",
|
"prettier": "^2.8.1",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^4.9.3",
|
||||||
"vite": "^4.0.0",
|
"vite": "^4.0.0",
|
||||||
"vite-plugin-html": "^3.2.0",
|
"vite-plugin-html": "^3.2.0",
|
||||||
|
"vite-plugin-vue-setup-extend": "^0.4.0",
|
||||||
"vue-tsc": "^1.0.11"
|
"vue-tsc": "^1.0.11"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"**/*.{vue,js,jsx,ts,tsx}": [
|
||||||
|
"npm run lint",
|
||||||
|
"prettier --write"
|
||||||
|
],
|
||||||
|
"**/*.{html,css,less,md}": [
|
||||||
|
"prettier --write"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 159 B |
Binary file not shown.
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 551 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="18" height="15" viewBox="0 0 18 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 1.12527V13.5414C0 14.2185 0.476821 14.6667 1.20199 14.6667H16.798C17.5132 14.6667 18 14.2185 18 13.5414V1.12527C18 0.448201 17.5232 0 16.798 0H1.20199C0.486755 0 0 0.448201 0 1.12527ZM1.20199 1.12527H16.798V4.51062H1.20199V1.12527ZM16.798 13.5414H1.20199V10.156H16.798V13.5414ZM1.20199 9.03077V5.63589H16.798V9.02124L1.20199 9.03077ZM13.798 2.25054H15C15.3576 2.25054 15.596 2.47941 15.596 2.81318C15.596 3.15648 15.3576 3.37581 15 3.37581H13.798C13.4404 3.37581 13.202 3.14694 13.202 2.81318C13.202 2.47941 13.4404 2.25054 13.798 2.25054ZM15.596 7.33333C15.596 7.67664 15.3576 7.89597 15 7.89597H13.798C13.4404 7.89597 13.202 7.6671 13.202 7.33333C13.202 6.99957 13.4404 6.7707 13.798 6.7707H15C15.3576 6.7707 15.596 6.99003 15.596 7.33333ZM13.202 11.844C13.202 11.5006 13.4404 11.2813 13.798 11.2813H15C15.3576 11.2813 15.596 11.5102 15.596 11.844C15.596 12.1873 15.3576 12.4066 15 12.4066H13.798C13.4404 12.4161 13.202 12.1873 13.202 11.844Z" fill="#597EF7"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="12" height="16" viewBox="0 0 12 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.49722 15.875C3.66823 15.875 4.61944 14.9238 4.61944 13.7528C4.61944 12.8205 4.01688 12.0285 3.17936 11.7424V4.39228C4.01688 4.10616 4.61944 3.31412 4.61944 2.38186C4.61944 1.21085 3.66823 0.259636 2.49722 0.259636C1.32621 0.259636 0.375 1.21085 0.375 2.38186C0.375 3.31412 0.977559 4.10805 1.81508 4.39228V11.7424C0.977559 12.0285 0.375 12.8205 0.375 13.7528C0.375 14.9238 1.32621 15.875 2.49722 15.875ZM1.86055 14.384C1.69051 14.2174 1.59256 13.9908 1.5877 13.7528C1.59256 13.5148 1.69051 13.2882 1.86055 13.1216C2.03059 12.9549 2.25916 12.8616 2.49722 12.8616C2.73527 12.8616 2.96385 12.9549 3.13388 13.1216C3.30392 13.2882 3.40188 13.5148 3.40674 13.7528C3.40188 13.9908 3.30392 14.2174 3.13388 14.384C2.96385 14.5506 2.73527 14.6439 2.49722 14.6439C2.25916 14.6439 2.03059 14.5506 1.86055 14.384ZM3.13388 1.75252C3.30392 1.91913 3.40188 2.14574 3.40674 2.38375C3.40188 2.62176 3.30392 2.84838 3.13388 3.01498C2.96385 3.18158 2.73527 3.2749 2.49722 3.2749C2.25916 3.2749 2.03059 3.18158 1.86055 3.01498C1.69051 2.84838 1.59256 2.62176 1.5877 2.38375C1.59256 2.14574 1.69051 1.91913 1.86055 1.75252C2.03059 1.58592 2.25916 1.49261 2.49722 1.49261C2.73527 1.49261 2.96385 1.58592 3.13388 1.75252ZM11.3016 12.8713H9.84711V11.2344H9.84952V4.25811C10.687 3.97199 11.2896 3.17994 11.2896 2.24768C11.2896 1.07667 10.3384 0.125463 9.16738 0.125463C7.99637 0.125463 7.04516 1.07667 7.04516 2.24768C7.04516 3.17994 7.64772 3.97388 8.48524 4.25811V10.4632L8.48351 10.4633L8.48524 10.8587V11.2344H8.48688L8.49405 12.8734H7.04163C6.91267 12.8734 6.8429 13.0214 6.92113 13.1208L9.05219 15.8163C9.0663 15.8346 9.0844 15.8494 9.1051 15.8595C9.12581 15.8697 9.14857 15.875 9.17164 15.875C9.19471 15.875 9.21747 15.8697 9.23818 15.8595C9.25889 15.8494 9.27699 15.8346 9.29109 15.8163L11.4222 13.1187C11.5004 13.0193 11.4285 12.8713 11.3016 12.8713ZM10.0769 2.2485C10.072 2.0105 9.97405 1.78388 9.80401 1.61727C9.63397 1.45067 9.4054 1.35736 9.16735 1.35736C8.92929 1.35736 8.70072 1.45067 8.53068 1.61727C8.36064 1.78388 8.26268 2.0105 8.25782 2.2485C8.26268 2.48651 8.36064 2.71313 8.53068 2.87973C8.70072 3.04634 8.92929 3.13965 9.16735 3.13965C9.4054 3.13965 9.63397 3.04634 9.80401 2.87973C9.97405 2.71313 10.072 2.48651 10.0769 2.2485Z" fill="#597EF7"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
|
@ -0,0 +1,4 @@
|
||||||
|
import { get } from '@/utils/request'
|
||||||
|
|
||||||
|
// 三方应用账户信息
|
||||||
|
export const applicationInfo = (code: string) => get(`/application/sso/bind-code/${code}`)
|
|
@ -0,0 +1,3 @@
|
||||||
|
import server from '@/utils/request'
|
||||||
|
|
||||||
|
export const deleteMetadata = (deviceId: string) => server.remove(`/device-instance/${deviceId}/metadata`)
|
|
@ -0,0 +1,8 @@
|
||||||
|
import server from '@/utils/request';
|
||||||
|
|
||||||
|
// 设备数量
|
||||||
|
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);
|
|
@ -0,0 +1,3 @@
|
||||||
|
import server from '@/utils/request'
|
||||||
|
|
||||||
|
export const save = (data) => server.post(`/network/certificate`, data)
|
|
@ -0,0 +1,15 @@
|
||||||
|
import server from '@/utils/request'
|
||||||
|
|
||||||
|
export const config = () => server.get(`/authorize/captcha/config`)
|
||||||
|
|
||||||
|
export const code = () => server.get(`/authorize/captcha/image?width=130&height=30`)
|
||||||
|
|
||||||
|
export const authLogin = (data) => server.post(`/authorize/login`, data)
|
||||||
|
|
||||||
|
export const getInitSet = () => server.get(`/user/settings/init`)
|
||||||
|
|
||||||
|
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`)
|
|
@ -0,0 +1,6 @@
|
||||||
|
import server from '@/utils/request';
|
||||||
|
|
||||||
|
// 获取tree数据-第一层
|
||||||
|
export const getTreeOne_api = () => server.get(`/v3/api-docs/swagger-config`);
|
||||||
|
// 获取tree数据-第二层
|
||||||
|
export const getTreeTwo_api = (name:string) => server.get(`/v3/api-docs/${name}`);
|
|
@ -0,0 +1,13 @@
|
||||||
|
<template>
|
||||||
|
<div class=''>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup type='ts' name='FormBuilder'>
|
||||||
|
const data = reactive({})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,3 @@
|
||||||
|
import FormBuilder from './FormBuilder.vue'
|
||||||
|
|
||||||
|
export default FormBuilder
|
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<template v-if="isPermission">
|
||||||
|
<template v-if="popConfirm">
|
||||||
|
<a-popconfirm v-bind="popConfirm" :disabled="!isPermission || props.disabled">
|
||||||
|
<a-tooltip v-if="tooltip" v-bind="tooltip">
|
||||||
|
<slot v-if="noButton"></slot>
|
||||||
|
<a-button v-else v-bind="buttonProps" :disabled="_isPermission"></a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-button v-else v-bind="buttonProps" :disabled="_isPermission"></a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="tooltip">
|
||||||
|
<a-tooltip v-bind="tooltip">
|
||||||
|
<slot v-if="noButton"></slot>
|
||||||
|
<a-button v-else v-bind="buttonProps" :disabled="_isPermission"></a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<slot v-if="noButton"></slot>
|
||||||
|
<a-button v-else v-bind="buttonProps" :disabled="_isPermission"></a-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<a-tooltip v-else title="没有权限">
|
||||||
|
<slot v-if="noButton"></slot>
|
||||||
|
<a-button v-else v-bind="buttonProps" :disabled="_isPermission"></a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { ButtonProps, TooltipProps, PopconfirmProps } from 'ant-design-vue'
|
||||||
|
import { usePermissionStore } from '@/store/permission';
|
||||||
|
|
||||||
|
interface PermissionButtonProps extends ButtonProps {
|
||||||
|
tooltip?: TooltipProps;
|
||||||
|
popConfirm?: PopconfirmProps;
|
||||||
|
hasPermission?: string | Array<string>;
|
||||||
|
noButton?: boolean;
|
||||||
|
}
|
||||||
|
const props = withDefaults(defineProps<PermissionButtonProps>(), {
|
||||||
|
noButton: false
|
||||||
|
})
|
||||||
|
const { tooltip, popConfirm, hasPermission, noButton, ...buttonProps } = props;
|
||||||
|
|
||||||
|
const permissionStore = usePermissionStore()
|
||||||
|
|
||||||
|
const isPermission = computed(() => {
|
||||||
|
if (!props.hasPermission) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return permissionStore.hasPermission(props.hasPermission)
|
||||||
|
})
|
||||||
|
const _isPermission = computed(() =>
|
||||||
|
'hasPermission' in props && isPermission
|
||||||
|
? 'disabled' in buttonProps
|
||||||
|
? buttonProps.disabled
|
||||||
|
: false
|
||||||
|
: true
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,168 @@
|
||||||
|
<template>
|
||||||
|
<div class="jtable-body">
|
||||||
|
<div class="jtable-body-header">
|
||||||
|
<div class="jtable-body-header-left">
|
||||||
|
<slot name="headerTitle"></slot>
|
||||||
|
</div>
|
||||||
|
<div class="jtable-body-header-right">
|
||||||
|
<div class="jtable-setting-item" :class="[ModelEnum.CARD === model ? 'active' : '']" @click="modelChange(ModelEnum.CARD)">
|
||||||
|
<AppstoreOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="jtable-setting-item" :class="[ModelEnum.TABLE === model ? 'active' : '']" @click="modelChange(ModelEnum.TABLE)">
|
||||||
|
<UnorderedListOutlined />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="jtable-content">
|
||||||
|
<div v-if="model === ModelEnum.CARD" class="jtable-card">
|
||||||
|
<div
|
||||||
|
v-if="dataSource.length"
|
||||||
|
class="jtable-card-items"
|
||||||
|
:style="{gridTemplateColumns: `repeat(${column}, 1fr)`}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="jtable-card-item"
|
||||||
|
v-for="(item, index) in dataSource"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<slot name="cardRender" :item="item" :index="index"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<a-table :columns="columns" :dataSource="dataSource" :pagination="false" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="jtable-pagination" v-if="dataSource.length">
|
||||||
|
<a-pagination
|
||||||
|
size="small"
|
||||||
|
:total="50"
|
||||||
|
:show-total="total => `第 ${1} - ${1} 条/总共 ${total} 条`"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { UnorderedListOutlined, AppstoreOutlined } from '@ant-design/icons-vue'
|
||||||
|
import type { TableProps } from 'ant-design-vue/es/table'
|
||||||
|
import { Empty } from 'ant-design-vue'
|
||||||
|
|
||||||
|
enum ModelEnum {
|
||||||
|
TABLE = 'TABLE',
|
||||||
|
CARD = 'CARD',
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare type RequestData = {
|
||||||
|
code: string;
|
||||||
|
result: {
|
||||||
|
data: Record<string, any>[] | undefined;
|
||||||
|
pageIndex: number;
|
||||||
|
pageSize: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
status: number;
|
||||||
|
} & Record<string, any>;
|
||||||
|
|
||||||
|
interface JTableProps extends TableProps{
|
||||||
|
request: (params: Record<string, any> & {
|
||||||
|
pageSize: number;
|
||||||
|
pageIndex: number;
|
||||||
|
}) => Promise<Partial<RequestData>>;
|
||||||
|
cardBodyClass?: string;
|
||||||
|
columns: Record<string, any>[];
|
||||||
|
params: Record<string, any> & {
|
||||||
|
pageSize: number;
|
||||||
|
pageIndex: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// props和emit
|
||||||
|
const emit = defineEmits(["modelChange"]);
|
||||||
|
const props = withDefaults(defineProps<JTableProps>(), {
|
||||||
|
cardBodyClass: '',
|
||||||
|
request: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE
|
||||||
|
const model = ref<keyof typeof ModelEnum>(ModelEnum.CARD); // 模式切换
|
||||||
|
const column = ref<number>(4);
|
||||||
|
const dataSource = ref<Record<string, any>[]>([])
|
||||||
|
console.log(props)
|
||||||
|
// 方法
|
||||||
|
|
||||||
|
// 切换卡片和表格
|
||||||
|
const modelChange = (type: keyof typeof ModelEnum) => {
|
||||||
|
model.value = type
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求数据
|
||||||
|
const handleSearch = async (params1?: Record<string, any>) => {
|
||||||
|
const resp = await props.request({
|
||||||
|
pageSize: 10,
|
||||||
|
pageIndex: 1,
|
||||||
|
...params1
|
||||||
|
})
|
||||||
|
if(resp.status === 200){
|
||||||
|
dataSource.value = resp.result?.data || []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
handleSearch(props.params)
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.jtable-body {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 24px 24px;
|
||||||
|
background-color: white;
|
||||||
|
.jtable-body-header {
|
||||||
|
padding: 16px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
.jtable-body-header-right {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
.jtable-setting-item {
|
||||||
|
color: rgba(0, 0, 0, 0.75);
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @primary-color-hover;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: @primary-color-active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.jtable-content {
|
||||||
|
.jtable-card {
|
||||||
|
.jtable-card-items {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 26px;
|
||||||
|
// grid-template-columns: repeat(4, 1fr);
|
||||||
|
.jtable-card-item {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.jtable-pagination {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
// position: absolute;
|
||||||
|
// right: 24px;
|
||||||
|
// bottom: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,45 @@
|
||||||
|
.jtable-body {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 24px 24px;
|
||||||
|
background-color: white;
|
||||||
|
.jtable-body-header {
|
||||||
|
padding: 16px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
.jtable-body-header-right {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
.jtable-setting-item {
|
||||||
|
color: rgba(0, 0, 0, 0.75);
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @primary-color-hover;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: @primary-color-active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.jtable-content {
|
||||||
|
.jtable-card {
|
||||||
|
.jtable-card-items {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 26px;
|
||||||
|
// grid-template-columns: repeat(4, 1fr);
|
||||||
|
.jtable-card-item {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.jtable-pagination {
|
||||||
|
position: absolute;
|
||||||
|
right: 24px;
|
||||||
|
bottom: 24px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
import { UnorderedListOutlined, AppstoreOutlined } from '@ant-design/icons-vue'
|
||||||
|
import styles from './index.module.less'
|
||||||
|
import { Pagination, Table, Empty } from 'ant-design-vue'
|
||||||
|
import type { TableProps } from 'ant-design-vue/es/table'
|
||||||
|
|
||||||
|
enum ModelEnum {
|
||||||
|
TABLE = 'TABLE',
|
||||||
|
CARD = 'CARD',
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare type RequestData = {
|
||||||
|
code: string;
|
||||||
|
result: {
|
||||||
|
data: any[] | undefined;
|
||||||
|
pageIndex: number;
|
||||||
|
pageSize: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
status: number;
|
||||||
|
} & Record<string, any>;
|
||||||
|
|
||||||
|
interface JTableProps extends TableProps{
|
||||||
|
request: (params: Record<string, any> & {
|
||||||
|
pageSize: number;
|
||||||
|
pageIndex: number;
|
||||||
|
}) => Promise<Partial<RequestData>>;
|
||||||
|
cardBodyClass: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const JTable = defineComponent<JTableProps>({
|
||||||
|
name: 'JTable',
|
||||||
|
slots: [
|
||||||
|
'headerTitle', // 顶部左边插槽
|
||||||
|
'cardRender', // 卡片内容
|
||||||
|
],
|
||||||
|
emits: [
|
||||||
|
'modelChange', // 切换卡片和表格
|
||||||
|
],
|
||||||
|
props: {
|
||||||
|
cardBodyClass: '',
|
||||||
|
request: undefined,
|
||||||
|
columns: []
|
||||||
|
} as any,
|
||||||
|
setup(props ,{ slots, emit }){
|
||||||
|
const model = ref<keyof typeof ModelEnum>(ModelEnum.CARD); // 模式切换
|
||||||
|
const column = ref<number>(3);
|
||||||
|
console.log(props.columns, props.request)
|
||||||
|
const dataSource = ref<any[]>([
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
name: '胡彦斌',
|
||||||
|
age: 32,
|
||||||
|
address: '西湖区湖底公园1号',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
name: '胡彦祖1',
|
||||||
|
age: 42,
|
||||||
|
address: '西湖区湖底公园1号',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3',
|
||||||
|
name: '胡彦斌',
|
||||||
|
age: 32,
|
||||||
|
address: '西湖区湖底公园1号',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '4',
|
||||||
|
name: '胡彦祖1',
|
||||||
|
age: 42,
|
||||||
|
address: '西湖区湖底公园1号',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '5',
|
||||||
|
name: '胡彦斌',
|
||||||
|
age: 32,
|
||||||
|
address: '西湖区湖底公园1号',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '6',
|
||||||
|
name: '胡彦祖1',
|
||||||
|
age: 42,
|
||||||
|
address: '西湖区湖底公园1号',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
// 请求数据
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => <div class={styles["jtable-body"]}>
|
||||||
|
<div class={styles["jtable-body-header"]}>
|
||||||
|
<div class={styles["jtable-body-header-left"]}>
|
||||||
|
{/* 顶部左边插槽 */}
|
||||||
|
{slots.headerTitle && slots.headerTitle()}
|
||||||
|
</div>
|
||||||
|
<div class={styles["jtable-body-header-right"]}>
|
||||||
|
{/* <Space> */}
|
||||||
|
<div class={[styles["jtable-setting-item"], ModelEnum.CARD === model.value ? styles['active'] : '']} onClick={() => {
|
||||||
|
model.value = ModelEnum.CARD
|
||||||
|
}}>
|
||||||
|
<AppstoreOutlined />
|
||||||
|
</div>
|
||||||
|
<div class={[styles["jtable-setting-item"], ModelEnum.TABLE === model.value ? styles['active'] : '']} onClick={() => {
|
||||||
|
model.value = ModelEnum.TABLE
|
||||||
|
}}>
|
||||||
|
<UnorderedListOutlined />
|
||||||
|
</div>
|
||||||
|
{/* </Space> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* content */}
|
||||||
|
<div class={styles['jtable-content']}>
|
||||||
|
{
|
||||||
|
model.value === ModelEnum.CARD ?
|
||||||
|
<div class={styles['jtable-card']}>
|
||||||
|
{
|
||||||
|
dataSource.value.length ?
|
||||||
|
<div
|
||||||
|
class={styles['jtable-card-items']}
|
||||||
|
style={{gridTemplateColumns: `repeat(${column.value}, 1fr)`}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
dataSource.value.map(item => slots.cardRender ?
|
||||||
|
<div class={[styles['jtable-card-item'], props.cardBodyClass]}>{slots.cardRender(item)}</div>
|
||||||
|
: null)
|
||||||
|
}
|
||||||
|
</div> :
|
||||||
|
<div><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /></div>
|
||||||
|
}
|
||||||
|
</div> :
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
dataSource={dataSource.value}
|
||||||
|
columns={props.columns}
|
||||||
|
pagination={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{/* 分页 */}
|
||||||
|
{
|
||||||
|
dataSource.value.length &&
|
||||||
|
<div class={styles['jtable-pagination']}>
|
||||||
|
<Pagination
|
||||||
|
size="small"
|
||||||
|
total={50}
|
||||||
|
showTotal={(total) => {
|
||||||
|
const min = 1
|
||||||
|
const max = 1
|
||||||
|
return `第 ${min} - ${max} 条/总共 ${total} 条`
|
||||||
|
}}
|
||||||
|
onChange={() => {
|
||||||
|
|
||||||
|
}}
|
||||||
|
onShowSizeChange={() => {
|
||||||
|
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default JTable
|
|
@ -0,0 +1,41 @@
|
||||||
|
<template>
|
||||||
|
<div class="title">
|
||||||
|
<div class="title-before"></div>
|
||||||
|
<span>{{ data }}</span>
|
||||||
|
<slot name="extra"></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "TitleComponent",
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
|
||||||
|
.title {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.title-before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 4px;
|
||||||
|
height: calc(100% - 2px);
|
||||||
|
background-color: @primary-color;
|
||||||
|
border-radius: 0 3px 3px 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,8 +1,16 @@
|
||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
import AIcon from './AIcon'
|
import AIcon from './AIcon'
|
||||||
|
import PermissionButton from './PermissionButton/index.vue'
|
||||||
|
import JTable from './Table/index.vue'
|
||||||
|
import TitleComponent from "./TitleComponent/index.vue";
|
||||||
|
import Form from './Form'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
install(app: App) {
|
install(app: App) {
|
||||||
app.component('AIcon', AIcon)
|
app.component('AIcon', AIcon)
|
||||||
|
.component('PermissionButton', PermissionButton)
|
||||||
|
.component('JTable', JTable)
|
||||||
|
.component('TitleComponent', TitleComponent)
|
||||||
|
.component('Form', Form)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ const router = createRouter({
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
const token = LocalStore.get(TOKEN_KEY)
|
const token = LocalStore.get(TOKEN_KEY)
|
||||||
next() // 测试用, 可删除
|
|
||||||
if (token) {
|
if (token) {
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -20,13 +20,47 @@ export default [
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// start: 测试用, 可删除
|
// start: 测试用, 可删除
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
component: () => import('@/views/user/Login/index.vue')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/demo',
|
path: '/demo',
|
||||||
component: () => import('@/views/demo/index.vue')
|
component: () => import('@/views/demo/index.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/account/center/bind',
|
||||||
|
component: () => import('@/views/account/Center/bind/index.vue')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/iot/home',
|
path: '/iot/home',
|
||||||
component: () => import('@/views/iot/home/index.vue')
|
component: () => import('@/views/home/index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/table',
|
||||||
|
component: () => import('@/views/demo/table/index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/form',
|
||||||
|
component: () => import('@/views/demo/Form.vue')
|
||||||
},
|
},
|
||||||
// end: 测试用, 可删除
|
// end: 测试用, 可删除
|
||||||
|
|
||||||
|
// link 运维管理
|
||||||
|
{
|
||||||
|
path: '/link/log',
|
||||||
|
component: () => import('@/views/link/Log/index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/link/certificate',
|
||||||
|
component: () => import('@/views/link/Certificate/index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/link/certificate/detail/add',
|
||||||
|
component: () => import('@/views/link/Certificate/Detail/index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/link/accessConfig/detail/add',
|
||||||
|
component: () => import('@/views/link/AccessConfig/Detail/index.vue')
|
||||||
|
},
|
||||||
]
|
]
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export const usePermissionStore = defineStore({
|
||||||
|
id: 'permission',
|
||||||
|
state: () => ({
|
||||||
|
permissions: {} as {[key: string]: string},
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
check(state) {
|
||||||
|
return (permissionCode: string) => {
|
||||||
|
if (!permissionCode) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (!permissionCode.includes(":")) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const code = permissionCode.split(":")[0]
|
||||||
|
const value = permissionCode.split(":")[1]
|
||||||
|
const _buttonArray = state.permissions[code]
|
||||||
|
if (!_buttonArray) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return _buttonArray.includes(value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hasPermission(state) {
|
||||||
|
return (permissionCode: string | string[]) => {
|
||||||
|
if (!permissionCode) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (!!Object.keys(state.permissions).length) {
|
||||||
|
if (typeof permissionCode === 'string') {
|
||||||
|
return this.check(permissionCode)
|
||||||
|
}
|
||||||
|
return permissionCode.some(_permissionCode => this.check(_permissionCode))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { authLogin } from '@/api/login';
|
||||||
|
import { LocalStore } from '@/utils/comm';
|
||||||
|
import { TOKEN_KEY } from '@/utils/variable';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息
|
||||||
|
* @methods setUserInfos 设置用户信息
|
||||||
|
*/
|
||||||
|
export const useUserInfo = defineStore('userInfo', {
|
||||||
|
state: () => ({
|
||||||
|
userInfos: {
|
||||||
|
id: '',
|
||||||
|
username: '',
|
||||||
|
isAdmin: true,
|
||||||
|
currentAuthority: [],
|
||||||
|
expires: 0,
|
||||||
|
permissions: [],
|
||||||
|
roles: [],
|
||||||
|
token: '',
|
||||||
|
user: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
login(userInfo: any) {
|
||||||
|
const username = userInfo.userName.trim();
|
||||||
|
const password = userInfo.password;
|
||||||
|
const verifyCode = userInfo.verifyCode;
|
||||||
|
return new Promise((resolve: any, reject: any) => {
|
||||||
|
authLogin({ username, password, verifyCode })
|
||||||
|
.then((res: any) => {
|
||||||
|
Object.assign(this.userInfos, res.result);
|
||||||
|
LocalStore.set(TOKEN_KEY, res?.result.token);
|
||||||
|
resolve(res);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -29,5 +29,38 @@ export const StatusColorEnum = {
|
||||||
'default': 'default',
|
'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 default SystemConst;
|
||||||
|
|
|
@ -66,10 +66,10 @@ export const patch = function(url: string, data = {}) {
|
||||||
* GET method request
|
* GET method request
|
||||||
* @param {String} url
|
* @param {String} url
|
||||||
* @param {Object} [params]
|
* @param {Object} [params]
|
||||||
* @param {Object} [ext], 扩展参数
|
* @param {Object} [ext] 扩展参数
|
||||||
* @returns {AxiosInstance}
|
* @returns {AxiosInstance}
|
||||||
*/
|
*/
|
||||||
export const get = function(url: string, params = {}, ext: any) {
|
export const get = function(url: string, params = {}, ext?: any) {
|
||||||
return request({
|
return request({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url,
|
url,
|
||||||
|
@ -82,10 +82,10 @@ export const get = function(url: string, params = {}, ext: any) {
|
||||||
* DELETE method request
|
* DELETE method request
|
||||||
* @param {String} url
|
* @param {String} url
|
||||||
* @param {Object} [params]
|
* @param {Object} [params]
|
||||||
* @param {Object} [ext], 扩展参数
|
* @param {Object} [ext] 扩展参数
|
||||||
* @returns {AxiosInstance}
|
* @returns {AxiosInstance}
|
||||||
*/
|
*/
|
||||||
export const remove = function(url: string, params = {}, ext: any) {
|
export const remove = function(url: string, params = {}, ext?: any) {
|
||||||
return request({
|
return request({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url,
|
url,
|
||||||
|
@ -162,7 +162,7 @@ request.interceptors.request.use(config => {
|
||||||
// 让每个请求携带自定义 token 请根据实际情况自行修改
|
// 让每个请求携带自定义 token 请根据实际情况自行修改
|
||||||
const token = LocalStore.get(TOKEN_KEY)
|
const token = LocalStore.get(TOKEN_KEY)
|
||||||
// const token = store.$state.tokenAlias
|
// const token = store.$state.tokenAlias
|
||||||
if (token) {
|
if (!token) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
router.replace({
|
router.replace({
|
||||||
path: LoginPath
|
path: LoginPath
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
export const BASE_API_PATH = import.meta.env.VITE_APP_BASE_API
|
export const BASE_API_PATH = import.meta.env.VITE_APP_BASE_API
|
||||||
|
|
||||||
export const TOKEN_KEY = 'X-Access-Token'
|
export const TOKEN_KEY = 'X-Access-Token'
|
||||||
|
|
||||||
|
export const Version_Code = 'version_code'
|
||||||
|
|
||||||
|
export const NETWORK_CERTIFICATE_UPLOAD = '/network/certificate/upload'
|
|
@ -0,0 +1,286 @@
|
||||||
|
<!-- 第三方账户绑定 -->
|
||||||
|
<template>
|
||||||
|
<div class="page-container">
|
||||||
|
<div class="content">
|
||||||
|
<div class="title">第三方账户绑定</div>
|
||||||
|
<!-- 已登录-绑定三方账号 -->
|
||||||
|
<template v-if="false">
|
||||||
|
<div class="info">
|
||||||
|
<a-card style="width: 280px">
|
||||||
|
<template #title>
|
||||||
|
<div class="info-head">
|
||||||
|
<img :src="getImage('/bind/Rectangle.png')" />
|
||||||
|
<span>个人信息</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="info-body">
|
||||||
|
<img :src="getImage('/bind/jetlinksLogo.png')" />
|
||||||
|
<p>账号:admin</p>
|
||||||
|
<p>用户名:超级管理员</p>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
<img :src="getImage('/bind/Vector.png')" />
|
||||||
|
<a-card style="width: 280px">
|
||||||
|
<template #title>
|
||||||
|
<div class="info-head">
|
||||||
|
<img :src="getImage('/bind/Rectangle.png')" />
|
||||||
|
<span>三方账户信息</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="info-body">
|
||||||
|
<img :src="getImage('/bind/wechat-webapp.png')" />
|
||||||
|
<p>用户名:-</p>
|
||||||
|
<p>名称:微信昵称</p>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
<div class="btn">
|
||||||
|
<a-button type="primary">立即绑定</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- 未登录-绑定三方账号 -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="not-login">
|
||||||
|
<div class="logo">
|
||||||
|
<img :src="getImage('/bind/jetlinksLogo.png')" />
|
||||||
|
<img
|
||||||
|
class="arrow"
|
||||||
|
:src="getImage('/bind/Vector.png')"
|
||||||
|
/>
|
||||||
|
<img :src="getImage('/bind/wechat-webapp.png')" />
|
||||||
|
</div>
|
||||||
|
<div class="desc">
|
||||||
|
你已通过微信授权,完善以下登录信息即可以完成绑定
|
||||||
|
</div>
|
||||||
|
<div class="login-form">
|
||||||
|
<a-form layout="vertical">
|
||||||
|
<a-form-item
|
||||||
|
label="账户"
|
||||||
|
v-bind="validateInfos.username"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="formData.username"
|
||||||
|
placeholder="请输入账户"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
label="密码"
|
||||||
|
v-bind="validateInfos.password"
|
||||||
|
>
|
||||||
|
<a-input-password
|
||||||
|
v-model:value="formData.password"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
label="验证码"
|
||||||
|
v-bind="validateInfos.captcha"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="formData.captcha"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
>
|
||||||
|
<template #addonAfter>
|
||||||
|
<span style="cursor: pointer">
|
||||||
|
图形验证码
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
@click="handleSubmit"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
登录并绑定账户
|
||||||
|
</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { getImage } from '@/utils/comm';
|
||||||
|
import { Form } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { applicationInfo } from '@/api/bind';
|
||||||
|
|
||||||
|
const useForm = Form.useForm;
|
||||||
|
|
||||||
|
interface formData {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
captcha: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 三方应用信息
|
||||||
|
const getAppInfo = async () => {
|
||||||
|
const code: string = '73ab60c88979a1475963a5dde31e374b';
|
||||||
|
const res = await applicationInfo(code);
|
||||||
|
console.log('getAppInfo: ', res);
|
||||||
|
};
|
||||||
|
getAppInfo();
|
||||||
|
|
||||||
|
// 登录表单
|
||||||
|
const formData = ref<formData>({
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
captcha: '',
|
||||||
|
});
|
||||||
|
const formRules = ref({
|
||||||
|
username: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入账户',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入密码',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
captcha: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入验证码',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { resetFields, validate, validateInfos } = useForm(
|
||||||
|
formData.value,
|
||||||
|
formRules.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录并绑定账户
|
||||||
|
*/
|
||||||
|
const handleSubmit = () => {
|
||||||
|
validate()
|
||||||
|
.then(() => {
|
||||||
|
console.log('toRaw:', toRaw(formData.value));
|
||||||
|
console.log('formData.value:', formData.value);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('error', err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定成功跳转至页面url的: redirect
|
||||||
|
*/
|
||||||
|
const goRedirect = () => {
|
||||||
|
const urlParams = new URLSearchParams(window.location.hash);
|
||||||
|
const redirectUrl =
|
||||||
|
urlParams.get('redirect') ||
|
||||||
|
window.location.href.split('redirect=')?.[1];
|
||||||
|
console.log('redirectUrl: ', redirectUrl);
|
||||||
|
//内部集成需要跳回它们页面
|
||||||
|
if (redirectUrl && redirectUrl.indexOf('account/center/bind') === -1) {
|
||||||
|
window.location.href = decodeURIComponent(redirectUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(
|
||||||
|
.ant-form-item-label
|
||||||
|
> label.ant-form-item-required:not(
|
||||||
|
.ant-form-item-required-mark-optional
|
||||||
|
)::before
|
||||||
|
) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
:deep(.ant-form-item-label > label) {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.page-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
background: url(/images/bind/bindPage.png) 0% 0% / 100% 100% no-repeat;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
.content {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 850px;
|
||||||
|
min-height: 510px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e0e4e8;
|
||||||
|
border-radius: 2px;
|
||||||
|
.title {
|
||||||
|
margin: 30px 0;
|
||||||
|
color: #0f1222;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
font-family: 'PingFang SC';
|
||||||
|
font-style: normal;
|
||||||
|
line-height: 25px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
// 已登录-绑定三方账号
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
&-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
&-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
img {
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未登录
|
||||||
|
.not-login {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
img {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
.arrow {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: 'PingFang SC';
|
||||||
|
font-style: normal;
|
||||||
|
line-height: 14px;
|
||||||
|
opacity: 0.75;
|
||||||
|
mix-blend-mode: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<template>
|
||||||
|
<Form />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name='FormDemo'>
|
||||||
|
const data = reactive({})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -2,7 +2,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
父级: {{ testValue }}
|
父级: {{ testValue }}
|
||||||
<ViewItem v-model="testValue" />
|
<ValueItem v-model="testValue" />
|
||||||
<!-- 卡片 -->
|
<!-- 卡片 -->
|
||||||
<br />卡片组件:
|
<br />卡片组件:
|
||||||
<a-row :gutter="20">
|
<a-row :gutter="20">
|
||||||
|
@ -41,7 +41,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ViewItem from '@/components/ValueItem/index.vue';
|
|
||||||
import CardBox from '@/components/CardBox/index.vue';
|
import CardBox from '@/components/CardBox/index.vue';
|
||||||
import { StatusColorEnum } from '@/utils/consts';
|
import { StatusColorEnum } from '@/utils/consts';
|
||||||
import { getImage } from '@/utils/comm';
|
import { getImage } from '@/utils/comm';
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
<template>
|
||||||
|
<div class="box">
|
||||||
|
<JTable
|
||||||
|
:columns="[
|
||||||
|
{
|
||||||
|
title: '姓名',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '年龄',
|
||||||
|
dataIndex: 'age',
|
||||||
|
key: 'age',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '住址',
|
||||||
|
dataIndex: 'address',
|
||||||
|
key: 'address',
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
:request="request"
|
||||||
|
>
|
||||||
|
<template #headerTitle>
|
||||||
|
<a-button type="primary">新增</a-button>
|
||||||
|
</template>
|
||||||
|
<template #cardRender="slotProps">
|
||||||
|
<CardBox>
|
||||||
|
<template #content>
|
||||||
|
{{slotProps.item.name}}
|
||||||
|
</template>
|
||||||
|
</CardBox>
|
||||||
|
</template>
|
||||||
|
</JTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import server from "@/utils/request";
|
||||||
|
import CardBox from '@/components/CardBox/index.vue';
|
||||||
|
const request = (data: any) => server.post(`/device-product/_query`, data)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.box {
|
||||||
|
padding: 20px;
|
||||||
|
background: #f0f2f5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -13,20 +13,38 @@
|
||||||
<div class="box-item">
|
<div class="box-item">
|
||||||
<div class="label">CPU使用率</div>
|
<div class="label">CPU使用率</div>
|
||||||
<div class="value">{{ cpu }}</div>
|
<div class="value">{{ cpu }}</div>
|
||||||
<div class="chart" ref="cpuChart"></div>
|
<Pie
|
||||||
|
class="chart"
|
||||||
|
chart-ref="cpuChart"
|
||||||
|
:value="20"
|
||||||
|
:color-arr="['#ebebeb', '#d3adf7']"
|
||||||
|
image="/images/home/top-3.svg"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-item">
|
<div class="box-item">
|
||||||
<div class="label">JVM内存</div>
|
<div class="label">JVM内存</div>
|
||||||
<div class="value">{{ jvm }}</div>
|
<div class="value">{{ jvm }}</div>
|
||||||
<div class="chart" ref="jvmChart"></div>
|
<Pie
|
||||||
|
class="chart"
|
||||||
|
chart-ref="jvmChart"
|
||||||
|
:value="31"
|
||||||
|
:color-arr="['#d6e4ff', '#85a5ff']"
|
||||||
|
image="/images/home/top-4.svg"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const cpu = ref('0%');
|
import Pie from './Pie.vue';
|
||||||
const jvm = ref('10%');
|
|
||||||
|
const cpu = ref('20%');
|
||||||
|
const jvm = ref('31%');
|
||||||
|
|
||||||
|
const getData = ()=>{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
const jumpPage = () => {};
|
const jumpPage = () => {};
|
||||||
</script>
|
</script>
|
|
@ -6,12 +6,16 @@
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div
|
<div
|
||||||
class="box-item"
|
class="box-item"
|
||||||
v-for="(item,index) in cardData"
|
v-for="(item, index) in cardData"
|
||||||
@click="jumpPage(item)"
|
@click="jumpPage(item)"
|
||||||
>
|
>
|
||||||
<div class="item-english">{{ item.english }}</div>
|
<div class="item-english">{{ item.english }}</div>
|
||||||
<div class="item-title">{{ item.label }}</div>
|
<div class="item-title">{{ item.label }}</div>
|
||||||
<img class="item-image" :src="`/images/home/${index + 1}.png`" alt="" />
|
<img
|
||||||
|
class="item-image"
|
||||||
|
:src="`/images/home/${index + 1}.png`"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
@ -25,7 +29,7 @@ type configItem = {
|
||||||
link: string;
|
link: string;
|
||||||
english: string;
|
english: string;
|
||||||
label: string;
|
label: string;
|
||||||
save?: boolean;
|
params?: object;
|
||||||
};
|
};
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -35,13 +39,25 @@ const props = defineProps({
|
||||||
});
|
});
|
||||||
const { cardData, cardTitle } = toRefs(props);
|
const { cardData, cardTitle } = toRefs(props);
|
||||||
|
|
||||||
const jumpPage = (linkItem: configItem): void => {
|
const jumpPage = (row: configItem): void => {
|
||||||
if (linkItem.auth && linkItem.link) {
|
if (row.auth && row.link) {
|
||||||
router.push(`${linkItem.link}${linkItem.save ? '?save=true' : ''}`);
|
router.push(`${row.link}${objToParams(row.params || {})}`);
|
||||||
} else {
|
} else {
|
||||||
message.warning('暂无权限,请联系管理员');
|
message.warning('暂无权限,请联系管理员');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const objToParams = (source: object): string => {
|
||||||
|
if (Object.prototype.toString.call(source) === '[object Object]') {
|
||||||
|
const paramsArr = <any>[];
|
||||||
|
Object.entries(source).forEach(([prop, value]) => {
|
||||||
|
if (typeof value === 'object') value = JSON.stringify(value);
|
||||||
|
paramsArr.push(`${prop}=${value}`);
|
||||||
|
});
|
||||||
|
if (paramsArr.length > 0) return '?' + paramsArr.join('&');
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
|
@ -0,0 +1,137 @@
|
||||||
|
<template>
|
||||||
|
<a-card class="boot-card-container" :bordered="false">
|
||||||
|
<template #title>
|
||||||
|
<h5 class="title">{{ cardTitle }}</h5>
|
||||||
|
</template>
|
||||||
|
<div class="box">
|
||||||
|
<div
|
||||||
|
class="box-item"
|
||||||
|
v-for="(item, index) in cardData"
|
||||||
|
@click="jumpPage(item)"
|
||||||
|
>
|
||||||
|
<div class="item-content">
|
||||||
|
<div class="icon">
|
||||||
|
<img :src="item.image" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="intro">
|
||||||
|
<div class="item-english">{{ item.english }}</div>
|
||||||
|
<div class="item-label">{{ item.label }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-bj">
|
||||||
|
<img :src="`/images/home/home-${index + 1}.png`" alt="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
type configItem = {
|
||||||
|
auth: boolean;
|
||||||
|
link: string;
|
||||||
|
english: string;
|
||||||
|
label: string;
|
||||||
|
params?: object;
|
||||||
|
image: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const props = defineProps({
|
||||||
|
cardData: Array<configItem>,
|
||||||
|
cardTitle: String,
|
||||||
|
});
|
||||||
|
const { cardData, cardTitle } = toRefs(props);
|
||||||
|
|
||||||
|
const jumpPage = (row: configItem): void => {
|
||||||
|
if (row.auth && row.link) {
|
||||||
|
router.push(`${row.link}${objToParams(row.params || {})}`);
|
||||||
|
} else {
|
||||||
|
message.warning('暂无权限,请联系管理员');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const objToParams = (source: object): string => {
|
||||||
|
if (Object.prototype.toString.call(source) === '[object Object]') {
|
||||||
|
const paramsArr = <any>[];
|
||||||
|
// 直接使用for in遍历对象ts会报错
|
||||||
|
Object.entries(source).forEach(([prop, value]) => {
|
||||||
|
if (typeof value === 'object') value = JSON.stringify(value);
|
||||||
|
paramsArr.push(`${prop}=${value}`);
|
||||||
|
});
|
||||||
|
if (paramsArr.length > 0) return '?' + paramsArr.join('&');
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.boot-card-container {
|
||||||
|
:deep(.ant-card-body) {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-left: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background-color: #1d39c4;
|
||||||
|
border: 1px solid #b4c0da;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
content: ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.box {
|
||||||
|
font-size: 14px;
|
||||||
|
.box-item {
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
border-width: 1px 1px 1px 2px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: rgb(238, 238, 238) rgb(238, 238, 238)
|
||||||
|
rgb(238, 238, 238) rgb(133, 165, 255);
|
||||||
|
padding: 11px;
|
||||||
|
background: linear-gradient(
|
||||||
|
135.62deg,
|
||||||
|
#f6f7fd 22.27%,
|
||||||
|
rgba(255, 255, 255, 0.86) 91.82%
|
||||||
|
);
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: 0 4px 18px #efefef;
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.item-label {
|
||||||
|
color: #252526;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-bj {
|
||||||
|
position: absolute;
|
||||||
|
right: 10%;
|
||||||
|
bottom: 0;
|
||||||
|
width: 37px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,190 @@
|
||||||
|
<template>
|
||||||
|
<div class="comprehensive-home-conatiner">
|
||||||
|
<a-row :gutter="24" class="top" style="margin-bottom: 24px">
|
||||||
|
<a-col :span="6" class="left">
|
||||||
|
<BootCardSmall
|
||||||
|
:cardData="devBootConfig"
|
||||||
|
cardTitle="物联网引导"
|
||||||
|
/>
|
||||||
|
<div style="width: 100%; height: 24px"></div>
|
||||||
|
<BootCardSmall :cardData="opsBootConfig" cardTitle="运维引导" />
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="18" class="right">
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="12"><DeviceCountCard /></a-col>
|
||||||
|
<a-col :span="12"><BasicCountCard /></a-col>
|
||||||
|
<a-col :span="24" style="margin-top: 24px">
|
||||||
|
<PlatformPicCard image="/images/home/content1.png" />
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<StepCard
|
||||||
|
cardTitle="设备接入推荐步骤"
|
||||||
|
tooltip="不同的设备因为通信协议的不同,存在接入步骤的差异"
|
||||||
|
:dataList="devStepDetails"
|
||||||
|
style="margin-bottom: 24px"
|
||||||
|
/>
|
||||||
|
<StepCard
|
||||||
|
cardTitle="运维管理推荐步骤"
|
||||||
|
tooltip="请根据业务需要对下述步骤进行选择性操作。"
|
||||||
|
:dataList="opsStepDetails"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="ComprehensiveHome">
|
||||||
|
import BootCardSmall from '../BootCardSmall.vue';
|
||||||
|
import DeviceCountCard from '../DeviceCountCard.vue';
|
||||||
|
import BasicCountCard from '../BasicCountCard.vue';
|
||||||
|
import PlatformPicCard from '../PlatformPicCard.vue';
|
||||||
|
import { recommendList } from '../../index';
|
||||||
|
|
||||||
|
import StepCard from '../StepCard.vue';
|
||||||
|
|
||||||
|
// 物联网引导-数据
|
||||||
|
const devBootConfig = [
|
||||||
|
{
|
||||||
|
english: 'STEP1',
|
||||||
|
label: '创建产品',
|
||||||
|
link: '/a',
|
||||||
|
auth: true,
|
||||||
|
save: true,
|
||||||
|
image: '/images/home/guide-home1.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
english: 'STEP2',
|
||||||
|
label: '创建设备',
|
||||||
|
link: '/b',
|
||||||
|
auth: true,
|
||||||
|
save: true,
|
||||||
|
image: '/images/home/guide-home2.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
english: 'STEP3',
|
||||||
|
label: '规则引擎',
|
||||||
|
link: '/c',
|
||||||
|
auth: false,
|
||||||
|
save: true,
|
||||||
|
image: '/images/home/guide-home3.png',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
// 运维管理引导-数据
|
||||||
|
const opsBootConfig = [
|
||||||
|
{
|
||||||
|
english: 'STEP1',
|
||||||
|
label: '创建产品',
|
||||||
|
link: '/a',
|
||||||
|
auth: true,
|
||||||
|
save: true,
|
||||||
|
image: '/images/home/guide-home4.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
english: 'STEP2',
|
||||||
|
label: '创建设备',
|
||||||
|
link: '/b',
|
||||||
|
auth: true,
|
||||||
|
save: true,
|
||||||
|
image: '/images/home/guide-home5.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
english: 'STEP3',
|
||||||
|
label: '规则引擎',
|
||||||
|
link: '/c',
|
||||||
|
auth: false,
|
||||||
|
save: true,
|
||||||
|
image: '/images/home/guide-home6.png',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
// 设备接入推荐步骤-数据
|
||||||
|
const devStepDetails = [
|
||||||
|
{
|
||||||
|
title: '创建产品',
|
||||||
|
details:
|
||||||
|
'产品是设备的集合,通常指一组具有相同功能的设备。物联设备必须通过产品进行接入方式配置。',
|
||||||
|
iconUrl: '/images/home/bottom-4.png',
|
||||||
|
linkUrl: '/a',
|
||||||
|
auth: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '配置产品接入方式',
|
||||||
|
details:
|
||||||
|
'通过产品对同一类型的设备进行统一的接入方式配置。请参照设备铭牌说明选择匹配的接入方式。',
|
||||||
|
iconUrl: '/images/home/bottom-1.png',
|
||||||
|
linkUrl: '/a',
|
||||||
|
auth: true,
|
||||||
|
dialogTag: 'accessMethod',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '添加测试设备',
|
||||||
|
details: '添加单个设备,用于验证产品模型是否配置正确。',
|
||||||
|
iconUrl: '/images/home/bottom-5.png',
|
||||||
|
linkUrl: '/a',
|
||||||
|
auth: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '功能调试',
|
||||||
|
details:
|
||||||
|
'对添加的测试设备进行功能调试,验证能否连接到平台,设备功能是否配置正确。',
|
||||||
|
iconUrl: '/images/home/bottom-2.png',
|
||||||
|
linkUrl: '/a',
|
||||||
|
auth: true,
|
||||||
|
dialogTag: 'funcTest',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '批量添加设备',
|
||||||
|
details: '批量添加同一产品下的设备',
|
||||||
|
iconUrl: '/images/home/bottom-3.png',
|
||||||
|
linkUrl: '/a',
|
||||||
|
auth: false,
|
||||||
|
},
|
||||||
|
] as recommendList[];
|
||||||
|
// 运维管理推荐步骤-数据
|
||||||
|
const opsStepDetails = [
|
||||||
|
{
|
||||||
|
title: '协议管理',
|
||||||
|
details:
|
||||||
|
'根据业务需求自定义开发对应的产品(设备模型)接入协议,并上传到平台。',
|
||||||
|
iconUrl: '/images/home/bottom-1.png',
|
||||||
|
linkUrl: '/a',
|
||||||
|
auth: true,
|
||||||
|
params: {
|
||||||
|
a: 1,
|
||||||
|
save: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '证书管理',
|
||||||
|
details: '统一维护平台内的证书,用于数据通信加密。',
|
||||||
|
iconUrl: '/images/home/bottom-6.png',
|
||||||
|
linkUrl: '/a',
|
||||||
|
auth: true,
|
||||||
|
params: {
|
||||||
|
a: 1,
|
||||||
|
save: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '网络组件',
|
||||||
|
details: '根据不同的传输类型配置平台底层网络组件相关参数。',
|
||||||
|
iconUrl: '/images/home/bottom-3.png',
|
||||||
|
linkUrl: '/a',
|
||||||
|
auth: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '设备接入网关',
|
||||||
|
details: '根据不同的传输类型,关联消息协议,配置设备接入网关相关参数。',
|
||||||
|
iconUrl: '/images/home/bottom-4.png',
|
||||||
|
linkUrl: '/a',
|
||||||
|
auth: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '日志管理',
|
||||||
|
details: '监控系统日志,及时处理系统异常。',
|
||||||
|
iconUrl: '/images/home/bottom-5.png',
|
||||||
|
linkUrl: '/a',
|
||||||
|
auth: false,
|
||||||
|
},
|
||||||
|
] as recommendList[];
|
||||||
|
</script>
|
|
@ -27,6 +27,7 @@ import BasicCountCard from '../BasicCountCard.vue';
|
||||||
import PlatformPicCard from '../PlatformPicCard.vue';
|
import PlatformPicCard from '../PlatformPicCard.vue';
|
||||||
import StepCard from '../StepCard.vue';
|
import StepCard from '../StepCard.vue';
|
||||||
|
|
||||||
|
import {recommendList} from '../../index'
|
||||||
// import {getImage} from '@/utils/comm'
|
// import {getImage} from '@/utils/comm'
|
||||||
|
|
||||||
// 运维引导-数据
|
// 运维引导-数据
|
||||||
|
@ -93,7 +94,7 @@ const stepDetails = [
|
||||||
linkUrl: '/a',
|
linkUrl: '/a',
|
||||||
auth: false,
|
auth: false,
|
||||||
},
|
},
|
||||||
];
|
] as recommendList[];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
|
@ -25,9 +25,22 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { getDeviceCount_api, getProductCount_api } from '@/api/home';
|
||||||
const projectNum = ref(0);
|
const projectNum = ref(0);
|
||||||
const deviceNum = ref(0);
|
const deviceNum = ref(0);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getData();
|
||||||
|
});
|
||||||
|
|
||||||
|
const getData = () => {
|
||||||
|
getDeviceCount_api().then((resp) => {
|
||||||
|
deviceNum.value = resp.result;
|
||||||
|
});
|
||||||
|
getProductCount_api().then((resp) => {
|
||||||
|
projectNum.value = resp.result;
|
||||||
|
});
|
||||||
|
};
|
||||||
const jumpPage = () => {};
|
const jumpPage = () => {};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -27,6 +27,8 @@ import DeviceCountCard from '../DeviceCountCard.vue';
|
||||||
import PlatformPicCard from '../PlatformPicCard.vue';
|
import PlatformPicCard from '../PlatformPicCard.vue';
|
||||||
import StepCard from '../StepCard.vue';
|
import StepCard from '../StepCard.vue';
|
||||||
|
|
||||||
|
import {recommendList} from '../../index'
|
||||||
|
|
||||||
// import {getImage} from '@/utils/comm'
|
// import {getImage} from '@/utils/comm'
|
||||||
|
|
||||||
// 物联网引导-数据
|
// 物联网引导-数据
|
||||||
|
@ -36,21 +38,21 @@ const bootConfig = [
|
||||||
label: '创建产品',
|
label: '创建产品',
|
||||||
link: '/a',
|
link: '/a',
|
||||||
auth: true,
|
auth: true,
|
||||||
save: true,
|
params: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
english: 'STEP2',
|
english: 'STEP2',
|
||||||
label: '创建设备',
|
label: '创建设备',
|
||||||
link: '/b',
|
link: '/b',
|
||||||
auth: true,
|
auth: true,
|
||||||
save: true,
|
params: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
english: 'STEP3',
|
english: 'STEP3',
|
||||||
label: '规则引擎',
|
label: '规则引擎',
|
||||||
link: '/c',
|
link: '/c',
|
||||||
auth: false,
|
auth: false,
|
||||||
save: true,
|
params: {},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
// 设备接入推荐步骤-数据
|
// 设备接入推荐步骤-数据
|
||||||
|
@ -91,7 +93,7 @@ const stepDetails = [
|
||||||
linkUrl: '/a',
|
linkUrl: '/a',
|
||||||
auth: false,
|
auth: false,
|
||||||
},
|
},
|
||||||
];
|
] as recommendList[];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
|
@ -0,0 +1,85 @@
|
||||||
|
<template>
|
||||||
|
<div class="init-home-container">
|
||||||
|
<div class="title">请选择首页视图</div>
|
||||||
|
|
||||||
|
<div class="choose-view">
|
||||||
|
<a-row class="view-content" :gutter="24">
|
||||||
|
<a-col
|
||||||
|
:span="8"
|
||||||
|
class="select-item"
|
||||||
|
:class="{ selected: selectId === '1' }"
|
||||||
|
@click="selectId = '1'"
|
||||||
|
>
|
||||||
|
<img src="/images/home/device.png" alt="" />
|
||||||
|
</a-col>
|
||||||
|
<a-col
|
||||||
|
:span="8"
|
||||||
|
class="select-item"
|
||||||
|
:class="{ selected: selectId === '2' }"
|
||||||
|
@click="selectId = '2'"
|
||||||
|
>
|
||||||
|
<img src="/images/home/ops.png" alt="" />
|
||||||
|
</a-col>
|
||||||
|
<a-col
|
||||||
|
:span="8"
|
||||||
|
class="select-item"
|
||||||
|
:class="{ selected: selectId === '3' }"
|
||||||
|
@click="selectId = '3'"
|
||||||
|
>
|
||||||
|
<img src="/images/home/comprehensive.png" alt="" />
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-button type="primary" class="btn" @click="confirm">确定</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const selectId = ref('1');
|
||||||
|
|
||||||
|
const confirm = ()=>{}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.init-home-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100vh - 150px);
|
||||||
|
padding: 20px;
|
||||||
|
background-color: white;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-top: 28px;
|
||||||
|
margin-bottom: 48px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 26px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose-view {
|
||||||
|
width: 100%;
|
||||||
|
.view-content {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
.select-item {
|
||||||
|
border: 2px solid transparent;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
border-color: #10239e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: block;
|
||||||
|
margin: 48px auto;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,65 @@
|
||||||
|
<template>
|
||||||
|
<div :ref="props.chartRef"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { ComponentInternalInstance } from 'vue';
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
chartRef: String,
|
||||||
|
value: Number,
|
||||||
|
image: String,
|
||||||
|
colorArr: Array<string>,
|
||||||
|
});
|
||||||
|
|
||||||
|
const options = computed(() => ({
|
||||||
|
color: props.colorArr || ['#D3ADF7', '#979AFF'],
|
||||||
|
graphic: [
|
||||||
|
{
|
||||||
|
type: 'image',
|
||||||
|
style: {
|
||||||
|
image: props.image || '',
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
},
|
||||||
|
left: 'center',
|
||||||
|
top: '41%',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['100%', '60%'],
|
||||||
|
center: ['50%', '50%'],
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
data: [100 - (props.value || 0), props.value],
|
||||||
|
itemStyle: {
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
|
||||||
|
watch(options, () => {
|
||||||
|
initChart();
|
||||||
|
});
|
||||||
|
const initChart = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
const myChart = echarts.init(proxy?.$refs[props.chartRef as string] as HTMLElement);
|
||||||
|
|
||||||
|
myChart.clear();
|
||||||
|
myChart.setOption(options.value);
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
myChart.resize();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
initChart();
|
||||||
|
</script>
|
|
@ -4,11 +4,19 @@
|
||||||
<span>平台架构图</span>
|
<span>平台架构图</span>
|
||||||
<p>PLATFORM ARCHITECTURE DIAGRAM</p>
|
<p>PLATFORM ARCHITECTURE DIAGRAM</p>
|
||||||
</div>
|
</div>
|
||||||
<img src="/images/home/content.png" class="bj" alt="" />
|
<img
|
||||||
|
:src="props.image || '/images/home/content.png'"
|
||||||
|
class="bj"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
</a-card>
|
</a-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
image: String,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
:deep(.ant-card-body) {
|
:deep(.ant-card-body) {
|
||||||
|
@ -18,7 +26,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 480px;
|
border-bottom: 1px solid #2f54eb;
|
||||||
|
|
||||||
.bj {
|
.bj {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -37,11 +45,11 @@
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
|
||||||
p{
|
p {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50px;
|
top: 50px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
opacity: .3;
|
opacity: 0.3;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,49 +11,80 @@
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="box-list">
|
<div class="box-list">
|
||||||
<div
|
<div class="list-item" v-for="item in dataList">
|
||||||
class="list-item"
|
<div class="box-top" @click="jumpPage(item)">
|
||||||
v-for="item in dataList"
|
|
||||||
@click="jumpPage(item)"
|
|
||||||
>
|
|
||||||
<div class="box-top">
|
|
||||||
<span class="top-title">{{ item.title }}</span>
|
<span class="top-title">{{ item.title }}</span>
|
||||||
<img :src="item.iconUrl" alt="" />
|
<img :src="item.iconUrl" alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="box-details">{{ item.details }}</div>
|
<div class="box-details">{{ item.details }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="dialogs">
|
||||||
|
<AccessMethodDialog
|
||||||
|
:open-number="openAccess"
|
||||||
|
@confirm="againJumpPage"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { PropType } from 'vue';
|
||||||
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
|
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
interface listConfig {
|
import AccessMethodDialog from './dialogs/AccessMethodDialog.vue';
|
||||||
title: string;
|
|
||||||
details: string;
|
import { recommendList } from '../index';
|
||||||
iconUrl: string;
|
|
||||||
|
type rowType = {
|
||||||
|
params: object;
|
||||||
linkUrl: string;
|
linkUrl: string;
|
||||||
params?: object;
|
};
|
||||||
auth: boolean;
|
|
||||||
}
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
cardTitle: String,
|
cardTitle: String,
|
||||||
tooltip: String,
|
tooltip: String,
|
||||||
dataList: Array<listConfig>,
|
dataList: Array as PropType<recommendList[]>,
|
||||||
});
|
});
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const { cardTitle, tooltip, dataList } = toRefs(props);
|
const { cardTitle, tooltip, dataList } = toRefs(props);
|
||||||
|
const openAccess = ref<number>(0);
|
||||||
const jumpPage = (row: listConfig) => {
|
const openFunc = ref<number>(0);
|
||||||
if (row.auth && row.linkUrl) {
|
let selectRow: recommendList | rowType = {
|
||||||
router.push(`${row.linkUrl}${row.params ? '?save=true' : ''}`);
|
params: {},
|
||||||
} else {
|
linkUrl: '',
|
||||||
message.warning('暂无权限,请联系管理员');
|
};
|
||||||
|
// 跳转页面
|
||||||
|
const jumpPage = (row: recommendList) => {
|
||||||
|
if (!row.auth) return message.warning('暂无权限,请联系管理员');
|
||||||
|
selectRow = row; // 二次跳转需要使用
|
||||||
|
if (row.dialogTag == 'accessMethod') return (openAccess.value += 1);
|
||||||
|
else if (row.dialogTag === 'funcTest') return (openFunc.value += 1);
|
||||||
|
else if (row.linkUrl) {
|
||||||
|
router.push(`${row.linkUrl}${objToParams(row.params || {})}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// 弹窗返回后的二次跳转
|
||||||
|
const againJumpPage = (paramsSource: object) => {
|
||||||
|
const params = { ...(selectRow.params || {}), ...paramsSource };
|
||||||
|
router.push(`${selectRow.linkUrl}${objToParams(params || {})}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const objToParams = (source: object): string => {
|
||||||
|
if (Object.prototype.toString.call(source) === '[object Object]') {
|
||||||
|
const paramsArr = <any>[];
|
||||||
|
// 直接使用for in遍历对象ts会报错
|
||||||
|
Object.entries(source).forEach(([prop, value]) => {
|
||||||
|
if (typeof value === 'object') value = JSON.stringify(value);
|
||||||
|
paramsArr.push(`${prop}=${value}`);
|
||||||
|
});
|
||||||
|
if (paramsArr.length > 0) return '?' + paramsArr.join('&');
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
|
@ -0,0 +1,98 @@
|
||||||
|
<template>
|
||||||
|
<div ref="modal" class="access-method-dialog-container"></div>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="visible"
|
||||||
|
title="选择产品"
|
||||||
|
style="width: 700px"
|
||||||
|
@ok="handleOk"
|
||||||
|
show-search
|
||||||
|
:filter-option="filterOption"
|
||||||
|
:getContainer="getContainer"
|
||||||
|
:maskClosable="false"
|
||||||
|
>
|
||||||
|
<a-form :model="form" name="basic" autocomplete="off" layout="vertical">
|
||||||
|
<a-form-item
|
||||||
|
label="产品"
|
||||||
|
name="productId"
|
||||||
|
:rules="[{ required: true, message: '该字段是必填字段' }]"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="form.productId"
|
||||||
|
style="width: 100%"
|
||||||
|
:options="productList"
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-button key="back" @click="visible = false">取消</a-button>
|
||||||
|
<a-button key="submit" type="primary" @click="handleOk"
|
||||||
|
>确认</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ComponentInternalInstance } from 'vue';
|
||||||
|
|
||||||
|
import { getProductList_api } from '@/api/home';
|
||||||
|
import { productItem } from '../../index';
|
||||||
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
|
const props = defineProps({
|
||||||
|
openNumber: Number,
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['confirm']);
|
||||||
|
const visible = ref<boolean>(false);
|
||||||
|
const form = ref({
|
||||||
|
productId: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const productList = ref<[productItem] | []>([]);
|
||||||
|
|
||||||
|
const getContainer = () => proxy?.$refs.modal as HTMLElement;
|
||||||
|
const getOptions = () => {
|
||||||
|
getProductList_api().then((resp) => {
|
||||||
|
productList.value = resp.result
|
||||||
|
.filter((i: any) => !i?.accessId)
|
||||||
|
.map((item: { name: any; id: any }) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
})) as [productItem];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleOk = () => {
|
||||||
|
emits('confirm', form.value);
|
||||||
|
visible.value = false;
|
||||||
|
};
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
|
};
|
||||||
|
watch(
|
||||||
|
() => props.openNumber,
|
||||||
|
() => {
|
||||||
|
visible.value = true;
|
||||||
|
form.value.productId = '';
|
||||||
|
getOptions();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.access-method-dialog-container {
|
||||||
|
:deep(.ant-modal-body) {
|
||||||
|
.ant-form {
|
||||||
|
.ant-form-item-label {
|
||||||
|
color: green;
|
||||||
|
.ant-form-item-required {
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
left: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,135 @@
|
||||||
|
<template>
|
||||||
|
<div ref="modal" class="func-test-dialog-container"></div>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="visible"
|
||||||
|
title="选择产品"
|
||||||
|
style="width: 700px"
|
||||||
|
@ok="handleOk"
|
||||||
|
:getContainer="getContainer"
|
||||||
|
:maskClosable="false"
|
||||||
|
>
|
||||||
|
<div class="search">
|
||||||
|
<a-select
|
||||||
|
v-model:value="form.key"
|
||||||
|
style="width: 100%"
|
||||||
|
:options="productList"
|
||||||
|
/>
|
||||||
|
<a-select
|
||||||
|
v-model:value="form.relation"
|
||||||
|
style="width: 100%"
|
||||||
|
:options="productList"
|
||||||
|
/>
|
||||||
|
<a-input v-model:value="form.keyValue" allow-clear />
|
||||||
|
|
||||||
|
<a-button type="primary" @click="clickSearch">
|
||||||
|
<template #icon><SearchOutlined /></template>
|
||||||
|
搜索
|
||||||
|
</a-button>
|
||||||
|
<a-button type="primary" @click="clickReset">
|
||||||
|
<template #icon><reload-outlined /></template>
|
||||||
|
重置
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<a-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="tableData"
|
||||||
|
:row-selection="{
|
||||||
|
onChange: (selectedRowKeys, selectedRows) =>
|
||||||
|
(selectItem = selectedRows),
|
||||||
|
type: 'radio',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
</a-table>
|
||||||
|
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-button key="back" @click="visible = false">取消</a-button>
|
||||||
|
<a-button key="submit" type="primary" @click="handleOk"
|
||||||
|
>确认</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ComponentInternalInstance } from 'vue';
|
||||||
|
import { SearchOutlined, ReloadOutlined } from '@ant-design/icons-vue';
|
||||||
|
|
||||||
|
import { productItem, deviceInfo } from '../../index';
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
|
const emits = defineEmits(['confirm']);
|
||||||
|
const props = defineProps({
|
||||||
|
openNumber: Number,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 弹窗控制
|
||||||
|
const visible = ref<boolean>(false);
|
||||||
|
const getContainer = () => proxy?.$refs.modal as HTMLElement;
|
||||||
|
const handleOk = () => {
|
||||||
|
emits('confirm', form.value);
|
||||||
|
visible.value = false;
|
||||||
|
};
|
||||||
|
watch(
|
||||||
|
() => props.openNumber,
|
||||||
|
() => {
|
||||||
|
clickReset();
|
||||||
|
getOptions();
|
||||||
|
clickSearch();
|
||||||
|
visible.value = true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 搜索部分
|
||||||
|
const form = ref({
|
||||||
|
key: '',
|
||||||
|
relation: '',
|
||||||
|
keyValue: '',
|
||||||
|
});
|
||||||
|
const productList = ref<[productItem] | []>([]);
|
||||||
|
const getOptions = () => {
|
||||||
|
productList.value = [];
|
||||||
|
};
|
||||||
|
const clickSearch = ()=>{
|
||||||
|
|
||||||
|
}
|
||||||
|
const clickReset = () => {
|
||||||
|
Object.entries(form.value).forEach(([prop]) => {
|
||||||
|
form.value[prop] = '';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表格部分
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'deviceId',
|
||||||
|
dataIndex: 'deviceId',
|
||||||
|
key: 'deviceId',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'deviceName',
|
||||||
|
dataIndex: 'deviceName',
|
||||||
|
key: 'deviceName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'productName',
|
||||||
|
dataIndex: 'productName',
|
||||||
|
key: 'productName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createTime',
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
key: 'createTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'status',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const tableData = ref<deviceInfo[]>([]);
|
||||||
|
const selectItem: deviceInfo | {} = {};
|
||||||
|
const getList = () => {};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,23 @@
|
||||||
|
// 推荐步骤每项
|
||||||
|
export interface recommendList {
|
||||||
|
title: string;
|
||||||
|
details: string;
|
||||||
|
iconUrl: string;
|
||||||
|
linkUrl: string;
|
||||||
|
params?: object;
|
||||||
|
auth: boolean;
|
||||||
|
dialogTag?: 'accessMethod' | 'funcTest';
|
||||||
|
}
|
||||||
|
// 产品列表里的每项
|
||||||
|
export interface productItem {
|
||||||
|
label: string;
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface deviceInfo {
|
||||||
|
deviceId: string,
|
||||||
|
deviceName: string,
|
||||||
|
productName: string,
|
||||||
|
createTime: string,
|
||||||
|
status: boolean
|
||||||
|
}
|
|
@ -4,7 +4,10 @@
|
||||||
<div class="left"></div>
|
<div class="left"></div>
|
||||||
<div class="content iot-home-container">
|
<div class="content iot-home-container">
|
||||||
<!-- <InitHome /> -->
|
<!-- <InitHome /> -->
|
||||||
<DeviceHome />
|
<!-- <DeviceHome /> -->
|
||||||
|
<!-- <DevOpsHome /> -->
|
||||||
|
<!-- <ComprehensiveHome /> -->
|
||||||
|
<ApiPage />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -12,6 +15,10 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import InitHome from './components/InitHome/index.vue';
|
import InitHome from './components/InitHome/index.vue';
|
||||||
import DeviceHome from './components/DeviceHome/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'
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@ -30,14 +37,12 @@ import DeviceHome from './components/DeviceHome/index.vue';
|
||||||
background: #352d85;
|
background: #352d85;
|
||||||
width: 210px;
|
width: 210px;
|
||||||
min-height: calc(100vh - 48px);
|
min-height: calc(100vh - 48px);
|
||||||
|
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
margin: 24px;
|
margin: 24px;
|
||||||
width: calc(100vw - 258px);
|
width: calc(100vw - 280px);
|
||||||
|
overflow: hidden;
|
||||||
min-height: calc(100vh - 96px);
|
min-height: calc(100vh - 96px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// .iot-home-container {
|
|
||||||
// }
|
|
||||||
</style>
|
</style>
|
|
@ -1,30 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="init-home-container">
|
|
||||||
<div class="title">请选择首页视图</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup></script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.init-home-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
height: calc(100vh - 150px);
|
|
||||||
padding: 20px;
|
|
||||||
background-color: white;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin-top: 28px;
|
|
||||||
margin-bottom: 48px;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 26px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
const MetworkTypeMapping = new Map();
|
||||||
|
MetworkTypeMapping.set('websocket-server', 'WEB_SOCKET_SERVER');
|
||||||
|
MetworkTypeMapping.set('http-server-gateway', 'HTTP_SERVER');
|
||||||
|
MetworkTypeMapping.set('udp-device-gateway', 'UDP');
|
||||||
|
MetworkTypeMapping.set('coap-server-gateway', 'COAP_SERVER');
|
||||||
|
MetworkTypeMapping.set('mqtt-client-gateway', 'MQTT_CLIENT');
|
||||||
|
MetworkTypeMapping.set('mqtt-server-gateway', 'MQTT_SERVER');
|
||||||
|
MetworkTypeMapping.set('tcp-server-gateway', 'TCP_SERVER');
|
||||||
|
|
||||||
|
const ProcotoleMapping = new Map();
|
||||||
|
ProcotoleMapping.set('websocket-server', 'WebSocket');
|
||||||
|
ProcotoleMapping.set('http-server-gateway', 'HTTP');
|
||||||
|
ProcotoleMapping.set('udp-device-gateway', 'UDP');
|
||||||
|
ProcotoleMapping.set('coap-server-gateway', 'CoAP');
|
||||||
|
ProcotoleMapping.set('mqtt-client-gateway', 'MQTT');
|
||||||
|
ProcotoleMapping.set('mqtt-server-gateway', 'MQTT');
|
||||||
|
ProcotoleMapping.set('tcp-server-gateway', 'TCP');
|
||||||
|
ProcotoleMapping.set('child-device', '');
|
||||||
|
|
||||||
|
const descriptionList = {
|
||||||
|
'udp-device-gateway':
|
||||||
|
'UDP可以让设备无需建立连接就可以与平台传输数据。在允许一定程度丢包的情况下,提供轻量化且简单的连接。',
|
||||||
|
'tcp-server-gateway':
|
||||||
|
'TCP服务是一种面向连接的、可靠的、基于字节流的传输层通信协议。设备可通过TCP服务与平台进行长链接,实时更新状态并发送消息。可自定义多种粘拆包规则,处理传输过程中可能发生的粘拆包问题。',
|
||||||
|
'websocket-server':
|
||||||
|
'WebSocket是一种在单个TCP连接上进行全双工通信的协议,允许服务端主动向客户端推送数据。设备通过WebSocket服务与平台进行长链接,实时更新状态并发送消息,且可以发布订阅消息',
|
||||||
|
'mqtt-client-gateway':
|
||||||
|
'MQTT是ISO 标准下基于发布/订阅范式的消息协议,具有轻量、简单、开放和易于实现的特点。平台使用指定的ID接入其他远程平台,订阅消息。也可添加用户名和密码校验。可设置最大消息长度。可统一设置共享的订阅前缀。',
|
||||||
|
'http-server-gateway':
|
||||||
|
'HTTP服务是一个简单的请求-响应的基于TCP的无状态协议。设备通过HTTP服务与平台进行灵活的短链接通信,仅支持设备和平台之间单对单的请求-响应模式',
|
||||||
|
'mqtt-server-gateway':
|
||||||
|
'MQTT是ISO 标准下基于发布/订阅范式的消息协议,具有轻量、简单、开放和易于实现的特点。提供MQTT的服务端,以供设备以长链接的方式接入平台。设备使用唯一的ID,也可添加用户名和密码校验。可设置最大消息长度。',
|
||||||
|
'coap-server-gateway':
|
||||||
|
'CoAP是针对只有少量的内存空间和有限的计算能力提供的一种基于UDP的协议。便于低功耗或网络受限的设备与平台通信,仅支持设备和平台之间单对单的请求-响应模式。',
|
||||||
|
};
|
||||||
|
|
||||||
|
const columnsMQTT = [
|
||||||
|
{
|
||||||
|
title: '分组',
|
||||||
|
dataIndex: 'group',
|
||||||
|
key: 'group',
|
||||||
|
ellipsis: true,
|
||||||
|
align: 'center',
|
||||||
|
width: 100,
|
||||||
|
scopedSlots: { customRender: 'group' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'topic',
|
||||||
|
dataIndex: 'topic',
|
||||||
|
key: 'topic',
|
||||||
|
scopedSlots: { customRender: 'topic' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '上下行',
|
||||||
|
dataIndex: 'stream',
|
||||||
|
key: 'stream',
|
||||||
|
ellipsis: true,
|
||||||
|
align: 'center',
|
||||||
|
width: 100,
|
||||||
|
scopedSlots: { customRender: 'stream' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '说明',
|
||||||
|
dataIndex: 'description',
|
||||||
|
key: 'description',
|
||||||
|
scopedSlots: { customRender: 'description' },
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const columnsHTTP = [
|
||||||
|
{
|
||||||
|
title: '分组',
|
||||||
|
dataIndex: 'group',
|
||||||
|
key: 'group',
|
||||||
|
ellipsis: true,
|
||||||
|
width: 100,
|
||||||
|
scopedSlots: { customRender: 'group' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '地址',
|
||||||
|
dataIndex: 'address',
|
||||||
|
key: 'address',
|
||||||
|
scopedSlots: { customRender: 'address' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '示例',
|
||||||
|
dataIndex: 'example',
|
||||||
|
key: 'example',
|
||||||
|
scopedSlots: { customRender: 'example' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '说明',
|
||||||
|
dataIndex: 'description',
|
||||||
|
key: 'description',
|
||||||
|
scopedSlots: { customRender: 'description' }
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export { MetworkTypeMapping, ProcotoleMapping, descriptionList, columnsMQTT, columnsHTTP };
|
|
@ -0,0 +1,135 @@
|
||||||
|
<template>
|
||||||
|
<a-card :bordered="false">
|
||||||
|
<TitleComponent data="自定义设备接入"></TitleComponent>
|
||||||
|
<div>
|
||||||
|
<a-row :gutter="[24, 24]">
|
||||||
|
<a-col :span="12" v-for="item in items" :key="item.id">
|
||||||
|
<div class="provider">
|
||||||
|
<div class="box">
|
||||||
|
<div class="left">
|
||||||
|
<div class="images">
|
||||||
|
<img :src="backMap.get(item.id)" />
|
||||||
|
</div>
|
||||||
|
<div class="context">
|
||||||
|
<div class="title">{{ item.name }}</div>
|
||||||
|
<div class="desc">
|
||||||
|
<a-tooltip :title="item.description">
|
||||||
|
{{ item.description || '' }}
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
@click="goProviders(item)"
|
||||||
|
>接入</a-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup name="AccessConfigDetail">
|
||||||
|
import { getImage } from '@/utils/comm';
|
||||||
|
import TitleComponent from '@/components/TitleComponent/index.vue';
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ id: 'mqtt-server-gateway', name: '测试1', description: '测试1' },
|
||||||
|
{ id: 'websocket-server', name: '测试2', description: '测试' },
|
||||||
|
{ id: 'coap-server-gateway', name: '测试3', description: '测试' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const backMap = new Map();
|
||||||
|
backMap.set('mqtt-server-gateway', getImage('/access/mqtt.png'));
|
||||||
|
backMap.set('websocket-server', getImage('/access/websocket.png'));
|
||||||
|
backMap.set('coap-server-gateway', getImage('/access/coap.png'));
|
||||||
|
backMap.set('tcp-server-gateway', getImage('/access/tcp.png'));
|
||||||
|
backMap.set('child-device', getImage('/access/child-device.png'));
|
||||||
|
backMap.set('http-server-gateway', getImage('/access/http.png'));
|
||||||
|
backMap.set('udp-device-gateway', getImage('/access/udp.png'));
|
||||||
|
backMap.set('mqtt-client-gateway', getImage('/access/mqtt-broke.png'));
|
||||||
|
|
||||||
|
const goProviders = (value: object) => {
|
||||||
|
console.log(111, value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.provider {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
background: url('/public/images/access/background.png') no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 40px;
|
||||||
|
display: block;
|
||||||
|
width: 15%;
|
||||||
|
min-width: 64px;
|
||||||
|
height: 2px;
|
||||||
|
background-image: url('/public/images/access/rectangle.png');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
// border: 1px #8da1f4 solid;
|
||||||
|
// border-bottom-left-radius: 10%;
|
||||||
|
// border-bottom-right-radius: 10%;
|
||||||
|
content: ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 24px rgba(#000, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
width: calc(100% - 70px);
|
||||||
|
.images {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.context {
|
||||||
|
width: calc(100% - 84px);
|
||||||
|
margin: 10px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
color: rgba(0, 0, 0, 0.55);
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 13px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<template>
|
||||||
|
<a-button type="primary" @click="handlAdd">新增</a-button>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup name="AccessConfigPage">
|
||||||
|
|
||||||
|
const handlAdd = (e: any) => {
|
||||||
|
console.log(111,e);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,91 @@
|
||||||
|
<template>
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<div>
|
||||||
|
<a-textarea
|
||||||
|
:rows="4"
|
||||||
|
@change="textChange"
|
||||||
|
v-model:value="keystoreBase64"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
/>
|
||||||
|
<a-upload
|
||||||
|
accept=".pem"
|
||||||
|
listType="text"
|
||||||
|
:action="`${BASE_API_PATH}${NETWORK_CERTIFICATE_UPLOAD}`"
|
||||||
|
:headers="{
|
||||||
|
[TOKEN_KEY]: LocalStore.get(TOKEN_KEY),
|
||||||
|
}"
|
||||||
|
:showUploadList="false"
|
||||||
|
@change="handleChange"
|
||||||
|
>
|
||||||
|
<a-button style="margin-top: 10px">
|
||||||
|
<upload-outlined />
|
||||||
|
上传文件</a-button
|
||||||
|
>
|
||||||
|
</a-upload>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="CertificateFile">
|
||||||
|
import { UploadOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import type { UploadChangeParam } from 'ant-design-vue';
|
||||||
|
import { LocalStore } from '@/utils/comm';
|
||||||
|
import {
|
||||||
|
BASE_API_PATH,
|
||||||
|
TOKEN_KEY,
|
||||||
|
NETWORK_CERTIFICATE_UPLOAD,
|
||||||
|
} from '@/utils/variable';
|
||||||
|
import type { UploadProps } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: () => '',
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: () => '',
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: () => '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const keystoreBase64 = ref(props.modelValue);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const handleChange = (info: UploadChangeParam) => {
|
||||||
|
loading.value = true;
|
||||||
|
if (info.file.status === 'done') {
|
||||||
|
message.success('上传成功!');
|
||||||
|
const result = info.file.response?.result;
|
||||||
|
keystoreBase64.value = result;
|
||||||
|
console.log(1114, result);
|
||||||
|
loading.value = false;
|
||||||
|
emit('change', result);
|
||||||
|
emit('update:modelValue', result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const textChange = (val: any) => {
|
||||||
|
val.name = props.name;
|
||||||
|
emit('change', val);
|
||||||
|
// emit('update:modelValue', val);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(v) => {
|
||||||
|
keystoreBase64.value = v;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -0,0 +1,204 @@
|
||||||
|
<template>
|
||||||
|
<a-card>
|
||||||
|
<a-row :gutter="[24, 24]" style="padding: 24px">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form
|
||||||
|
class="form"
|
||||||
|
layout="vertical"
|
||||||
|
:model="formData"
|
||||||
|
name="basic"
|
||||||
|
:label-col="{ span: 8 }"
|
||||||
|
:wrapper-col="{ span: 16 }"
|
||||||
|
autocomplete="off"
|
||||||
|
@finish="onFinish"
|
||||||
|
:rules="formRules"
|
||||||
|
>
|
||||||
|
<a-form-item label="证书标准" name="type">
|
||||||
|
<a-radio-group v-model:value="formData.type">
|
||||||
|
<a-radio-button
|
||||||
|
class="form-radio-button"
|
||||||
|
value="common"
|
||||||
|
>
|
||||||
|
<img :src="getImage('/certificate.png')" />
|
||||||
|
</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="证书名称" name="name">
|
||||||
|
<a-input
|
||||||
|
placeholder="请输入证书名称"
|
||||||
|
v-model:value="formData.name"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="证书文件" name="cert">
|
||||||
|
<CertificateFile
|
||||||
|
name="cert"
|
||||||
|
v-model:modelValue="formData.cert"
|
||||||
|
@change="changeFileValue"
|
||||||
|
placeholder='证书格式以"-----BEGIN CERTIFICATE-----"开头,以"-----END CERTIFICATE-----"结尾"'
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="证书私钥" name="key">
|
||||||
|
<CertificateFile
|
||||||
|
name="key"
|
||||||
|
v-model:modelValue="formData.key"
|
||||||
|
@change="changeFileValue"
|
||||||
|
placeholder='证书私钥格式以"-----BEGIN (RSA|EC) PRIVATE KEY-----"开头,以"-----END(RSA|EC) PRIVATE KEY-----"结尾。'
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="说明" name="description">
|
||||||
|
<a-textarea
|
||||||
|
placeholder="请输入说明"
|
||||||
|
v-model:value="formData.description"
|
||||||
|
:maxlength="200"
|
||||||
|
:rows="3"
|
||||||
|
showCount
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item>
|
||||||
|
<a-button
|
||||||
|
class="form-submit"
|
||||||
|
html-type="submit"
|
||||||
|
type="primary"
|
||||||
|
>保存</a-button
|
||||||
|
>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<div class="doc">
|
||||||
|
<h1>1. 概述</h1>
|
||||||
|
<div>
|
||||||
|
证书由受信任的数字证书颁发机构CA,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能,保障设备与平台间的数据传输安全。配置后可被网络组件引用。
|
||||||
|
</div>
|
||||||
|
<h1>2. 配置说明</h1>
|
||||||
|
<h2>1、证书文件</h2>
|
||||||
|
<div>
|
||||||
|
您可以使用文本编辑工具打开PEM或者CRT格式的证书文件,复制其中的内容并粘贴到该文本框,或者单击该文本框下的上传,并选择存储在本地计算机的证书文件,将文件内容上传到文本框。
|
||||||
|
</div>
|
||||||
|
<h2>2、证书私钥</h2>
|
||||||
|
<div>
|
||||||
|
填写证书私钥内容的PEM编码。
|
||||||
|
您可以使用文本编辑工具打开KEY格式的证书私钥文件,复制其中的内容并粘贴到该文本框,或者单击该文本框下的上传并选择存储在本地计算机的证书私钥文件,将文件内容上传到文本框。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup name="CertificateDetail">
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { getImage } from '@/utils/comm';
|
||||||
|
import CertificateFile from './CertificateFile.vue';
|
||||||
|
import type { UploadChangeParam } from 'ant-design-vue';
|
||||||
|
import { LocalStore } from '@/utils/comm';
|
||||||
|
import {
|
||||||
|
BASE_API_PATH,
|
||||||
|
TOKEN_KEY,
|
||||||
|
NETWORK_CERTIFICATE_UPLOAD,
|
||||||
|
} from '@/utils/variable';
|
||||||
|
import { save } from '@/api/link/certificate';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
type: 'common',
|
||||||
|
name: '',
|
||||||
|
cert: '',
|
||||||
|
key: '',
|
||||||
|
// configs: {
|
||||||
|
// cert: '',
|
||||||
|
// key: '',
|
||||||
|
// },
|
||||||
|
description: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRules = {
|
||||||
|
type: [{ required: true, message: '请选择证书标准', trigger: 'blur' }],
|
||||||
|
name: [
|
||||||
|
{ required: true, message: '请输入证书名称', trigger: 'blur' },
|
||||||
|
{ max: 64, message: '最多可输入64个字符' },
|
||||||
|
],
|
||||||
|
cert: [{ required: true, message: '请输入或上传文件', trigger: 'blur' }],
|
||||||
|
key: [{ required: true, message: '请输入或上传文件', trigger: 'blur' }],
|
||||||
|
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFinish = async (values: any) => {
|
||||||
|
values.configs = {
|
||||||
|
cert: formData.cert,
|
||||||
|
key: formData.key,
|
||||||
|
};
|
||||||
|
delete values.cert;
|
||||||
|
delete values.key;
|
||||||
|
|
||||||
|
const response = await save(values)
|
||||||
|
if (response.status === 200) {
|
||||||
|
message.success('操作成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeFileValue = (v: any) => {
|
||||||
|
formData[v.name] = v.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (info: UploadChangeParam) => {
|
||||||
|
loading.value = true;
|
||||||
|
if (info.file.status === 'done') {
|
||||||
|
message.success('上传成功!');
|
||||||
|
const result = info.file.response?.result;
|
||||||
|
formData.cert = result;
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.form {
|
||||||
|
.form-radio-button {
|
||||||
|
width: 148px;
|
||||||
|
height: 80px;
|
||||||
|
padding: 0;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.form-upload-button {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.form-submit {
|
||||||
|
background-color: @primary-color !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc {
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 24px;
|
||||||
|
overflow-y: auto;
|
||||||
|
color: rgba(#000, 0.8);
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: #fff;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 16px 0;
|
||||||
|
color: rgba(#000, 0.85);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 6px 10px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<template>
|
||||||
|
<a-button type="primary" @click="handlAdd">新增</a-button>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup name="CertificatePage">
|
||||||
|
|
||||||
|
const handlAdd = (e: any) => {
|
||||||
|
console.log(111,e);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<div>访问日志</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup name="SystemLog">
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<div>系统日志</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup name="AccessLog">
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,17 @@
|
||||||
|
<template>
|
||||||
|
<a-tabs v-model:activeKey="activeKey">
|
||||||
|
<a-tab-pane key="1" tab="访问日志">
|
||||||
|
<AccessLog />
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="2" tab="系统日志" force-render>
|
||||||
|
<SystemLog />
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup name="LogPage">
|
||||||
|
import { defineComponent, ref } from 'vue';
|
||||||
|
import AccessLog from './Access/index.vue';
|
||||||
|
import SystemLog from './System/index.vue';
|
||||||
|
|
||||||
|
const activeKey = ref('1');
|
||||||
|
</script>
|
|
@ -0,0 +1,71 @@
|
||||||
|
<template>
|
||||||
|
<a-tree
|
||||||
|
:tree-data="treeData"
|
||||||
|
@select="clickSelectItem"
|
||||||
|
showLine
|
||||||
|
class="left-tree-container"
|
||||||
|
>
|
||||||
|
<template #title="{ name }">
|
||||||
|
{{ name }}
|
||||||
|
</template>
|
||||||
|
</a-tree>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { TreeProps } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getTreeOne_api, getTreeTwo_api } from '@/api/system/apiPage';
|
||||||
|
|
||||||
|
type treeNodeTpye = {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
children?: treeNodeTpye[];
|
||||||
|
};
|
||||||
|
const emits = defineEmits(['select']);
|
||||||
|
|
||||||
|
const treeData: TreeProps['treeData'] = ref([]);
|
||||||
|
|
||||||
|
const getTreeData = () => {
|
||||||
|
let tree: treeNodeTpye[] = [];
|
||||||
|
getTreeOne_api().then((resp) => {
|
||||||
|
tree = resp.urls.map((item) => ({
|
||||||
|
...item,
|
||||||
|
key: item.url,
|
||||||
|
}));
|
||||||
|
const allPromise = tree.map((item) => getTreeTwo_api(item.name));
|
||||||
|
Promise.all(allPromise).then((values) => {
|
||||||
|
values.forEach((item, i) => {
|
||||||
|
tree[i].children = combData(item.paths);
|
||||||
|
});
|
||||||
|
console.log(tree);
|
||||||
|
treeData.value = tree
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const clickSelectItem = (key, { node }) => {
|
||||||
|
emits('select', node);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getTreeData();
|
||||||
|
});
|
||||||
|
|
||||||
|
const combData = (dataSource: object): object[] => {
|
||||||
|
const apiList: object[] = [];
|
||||||
|
const keys = Object.keys(dataSource);
|
||||||
|
|
||||||
|
keys.forEach((key) => {
|
||||||
|
const method = Object.keys(dataSource[key] || {})[0];
|
||||||
|
const name = dataSource[key][method].tags[0];
|
||||||
|
let apiObj = apiList.find((item) => item.name === name);
|
||||||
|
if (!apiObj) {
|
||||||
|
apiObj = { name, link: key, methods: dataSource[key], key };
|
||||||
|
apiList.push(apiObj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return apiList;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<template>
|
||||||
|
<a-card class="api-page-container" >
|
||||||
|
<LeftTree />
|
||||||
|
</a-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import LeftTree from './components/LeftTree.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.api-page-container {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,534 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-spin :spinning="loading" :delay="500">
|
||||||
|
<div class="container">
|
||||||
|
<div class="left">
|
||||||
|
<img
|
||||||
|
style="width: 100%; height: 100%"
|
||||||
|
:src="getImage('/login.png')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<div class="lang" data-lang=""></div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="top">
|
||||||
|
<div class="header">
|
||||||
|
<!-- <link to="/"> -->
|
||||||
|
<img
|
||||||
|
alt="logo"
|
||||||
|
class="logo"
|
||||||
|
:src="getImage('/logo.png')"
|
||||||
|
/>
|
||||||
|
<!-- </link> -->
|
||||||
|
</div>
|
||||||
|
<div class="desc">物联网平台</div>
|
||||||
|
<div class="main">
|
||||||
|
<a-form
|
||||||
|
layout="vertical"
|
||||||
|
:model="form"
|
||||||
|
class="login-form"
|
||||||
|
@finish="onFinish"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
label="账号"
|
||||||
|
name="username"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入账号!',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="form.username"
|
||||||
|
placeholder="请输入账号"
|
||||||
|
:maxlength="64"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
label="密码"
|
||||||
|
name="password"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入密码!',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<a-input-password
|
||||||
|
v-model:value="form.password"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
:maxlength="64"
|
||||||
|
></a-input-password>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
v-if="codeConfig"
|
||||||
|
class="verifyCode"
|
||||||
|
label="验证码"
|
||||||
|
name="verifyCode"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入验证码!',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
class="login-code-input"
|
||||||
|
v-model:value="form.verifyCode"
|
||||||
|
autocomplete="off"
|
||||||
|
:maxlength="64"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
></a-input>
|
||||||
|
<div class="login-code">
|
||||||
|
<img
|
||||||
|
:src="codeUrl"
|
||||||
|
@click="getCode()"
|
||||||
|
class="login-code-img"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
name="remember"
|
||||||
|
style="text-align: left"
|
||||||
|
>
|
||||||
|
<a-checkbox
|
||||||
|
v-model:checked="form.remember"
|
||||||
|
>记住密码</a-checkbox
|
||||||
|
>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-button
|
||||||
|
:loading="loading"
|
||||||
|
type="primary"
|
||||||
|
html-type="submit"
|
||||||
|
class="login-form-button"
|
||||||
|
block
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<div style="margin-top: 20px">
|
||||||
|
<a-divider plain style="height: 12px">
|
||||||
|
<div
|
||||||
|
style="color: #807676d9, font-size: 12px"
|
||||||
|
>
|
||||||
|
其他方式登录
|
||||||
|
</div>
|
||||||
|
</a-divider>
|
||||||
|
<div
|
||||||
|
style="position: relative, bottom: 10px; text-align: center"
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
v-for="(item, index) in bindings"
|
||||||
|
:key="index"
|
||||||
|
type="link"
|
||||||
|
@Click="handleClickOther(item)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style="width: 32px, height: 33px"
|
||||||
|
:alt="item.name"
|
||||||
|
:src="
|
||||||
|
iconMap.get(
|
||||||
|
item.provider,
|
||||||
|
) || defaultImg
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bottom">
|
||||||
|
<div class="view">
|
||||||
|
JETLINKS团队全新力作可视化大屏系统
|
||||||
|
</div>
|
||||||
|
<div class="url">
|
||||||
|
<div style="height: 33px">
|
||||||
|
<img :src="viewLogo" />
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="https://view.jetlinks.cn/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
体验DEMO
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { getImage } from '@/utils/comm';
|
||||||
|
import {
|
||||||
|
config,
|
||||||
|
code,
|
||||||
|
authLogin,
|
||||||
|
getInitSet,
|
||||||
|
systemVersion,
|
||||||
|
bindInfo,
|
||||||
|
} from '@/api/login';
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
|
import { LocalStore } from '@/utils/comm';
|
||||||
|
import { BASE_API_PATH, TOKEN_KEY, Version_Code } from '@/utils/variable';
|
||||||
|
|
||||||
|
const store = useUserInfo();
|
||||||
|
const router = useRouter();
|
||||||
|
const bgImage = getImage('/logo.png');
|
||||||
|
const viewLogo = getImage('/view-logo.png');
|
||||||
|
|
||||||
|
const LoginWarpStyle = reactive({
|
||||||
|
backgroundImage: `url(${bgImage})`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const screenWidth = ref(document.body.clientWidth);
|
||||||
|
const screenHeight = ref(document.body.clientHeight);
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
remember: false,
|
||||||
|
expires: 3600000,
|
||||||
|
verifyCode: '',
|
||||||
|
verifyKey: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const codeUrl = ref('');
|
||||||
|
const codeConfig = ref(false);
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const bindings = ref<any[]>();
|
||||||
|
|
||||||
|
const defaultImg = getImage('/apply/provider1.png');
|
||||||
|
const iconMap = new Map();
|
||||||
|
iconMap.set('dingtalk-ent-app', getImage('/bind/dingtalk.png'));
|
||||||
|
iconMap.set('wechat-webapp', getImage('/bind/wechat-webapp.png'));
|
||||||
|
|
||||||
|
const onFinish = async () => {
|
||||||
|
form.remember
|
||||||
|
? Cookies.set('user', encodeURIComponent(JSON.stringify(form)), {
|
||||||
|
expires: 7,
|
||||||
|
})
|
||||||
|
: Cookies.remove('user');
|
||||||
|
Cookies.set('username', form.username, { expires: 30 });
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const res: any = await authLogin(form);
|
||||||
|
if (res.success) {
|
||||||
|
store.$patch({
|
||||||
|
...res.result,
|
||||||
|
username: form.username,
|
||||||
|
});
|
||||||
|
LocalStore.set(TOKEN_KEY, res?.result.token);
|
||||||
|
const resp: any = await getInitSet();
|
||||||
|
if (resp.success) {
|
||||||
|
router.push('/demo');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
form.verifyCode = '';
|
||||||
|
getCode();
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCode = async () => {
|
||||||
|
const configRes: any = await config();
|
||||||
|
if (!configRes.success || (configRes.success && !configRes.result.enabled))
|
||||||
|
return;
|
||||||
|
|
||||||
|
codeConfig.value = true;
|
||||||
|
const res: any = await code();
|
||||||
|
if (res.success) {
|
||||||
|
codeUrl.value = res.result.base64;
|
||||||
|
form.verifyKey = res.result.key;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCookie = () => {
|
||||||
|
// form.username = Cookies.get('username');
|
||||||
|
if (!Cookies.get('user')) return;
|
||||||
|
const user = JSON.parse(decodeURIComponent(Cookies.get('user')));
|
||||||
|
form.username = user.username;
|
||||||
|
form.password = user.password;
|
||||||
|
form.remember = user.remember || false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOpen = () => {
|
||||||
|
systemVersion().then((res: any) => {
|
||||||
|
if (res.success && res.result) {
|
||||||
|
LocalStore.set(Version_Code, res.result.edition);
|
||||||
|
if (res.result.edition !== 'community') {
|
||||||
|
bindInfo().then((res: any) => {
|
||||||
|
if (res.success) {
|
||||||
|
bindings.value = res.result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickOther = (item: any) => {
|
||||||
|
LocalStore.set('onLogin', 'no');
|
||||||
|
window.open(`${BASE_API_PATH}/application/sso/${item.id}/login`);
|
||||||
|
window.onstorage = (e) => {
|
||||||
|
if (e.newValue) {
|
||||||
|
window.location.href = '/';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const screenRotation = (width: number, height: number) => {
|
||||||
|
LoginWarpStyle.backgroundImage = `url(${bgImage})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onresize = () => {
|
||||||
|
return (() => {
|
||||||
|
screenWidth.value = document.body.clientWidth;
|
||||||
|
screenHeight.value = document.body.clientHeight;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[() => screenWidth.value, () => screenHeight.value],
|
||||||
|
(value) => {
|
||||||
|
screenRotation(value[0], value[1]);
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
getOpen();
|
||||||
|
getCode();
|
||||||
|
getCookie();
|
||||||
|
screenRotation(screenWidth.value, screenHeight.value);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
@import 'ant-design-vue/es/style/themes/default.less';
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: auto;
|
||||||
|
background: @layout-body-background;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
width: 73%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 27%;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
:deep(.ant-layout-footer) {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang {
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 44px;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
:global(.ant-dropdown-trigger) {
|
||||||
|
margin-right: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 0 15% 0;
|
||||||
|
|
||||||
|
.top {
|
||||||
|
width: 100%;
|
||||||
|
// text-align: center;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
height: 44px;
|
||||||
|
line-height: 44px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 44px;
|
||||||
|
margin-right: 16px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
color: @heading-color;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 33px;
|
||||||
|
font-family: Avenir, 'Helvetica Neue', Arial, Helvetica,
|
||||||
|
sans-serif;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
// color: @heading-color;
|
||||||
|
color: rgb(0 0 0 / 70%);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 22px;
|
||||||
|
font-family: Avenir, 'Helvetica Neue', Arial, Helvetica,
|
||||||
|
sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
width: 70%;
|
||||||
|
margin: 60px auto 0;
|
||||||
|
|
||||||
|
@media screen and (max-width: @screen-sm) {
|
||||||
|
width: 95%;
|
||||||
|
max-width: 328px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ::v-deep {
|
||||||
|
// .@{ant-prefix}-tabs-nav-list {
|
||||||
|
// margin: auto;
|
||||||
|
// font-size: 16px;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // .ant-formily-item-size-large .ant-formily-item-help {
|
||||||
|
// // text-align: left;
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-left: 16px;
|
||||||
|
color: rgba(0, 0, 0, 0.2);
|
||||||
|
font-size: 24px;
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.other {
|
||||||
|
margin-top: 24px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
.register {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.prefixIcon {
|
||||||
|
color: @primary-color;
|
||||||
|
font-size: @font-size-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remember {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verifyCode {
|
||||||
|
.login-code-input {
|
||||||
|
width: 70%;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.login-code {
|
||||||
|
width: 30%;
|
||||||
|
height: 32px;
|
||||||
|
float: left;
|
||||||
|
background-color: #e4e6e7;
|
||||||
|
img {
|
||||||
|
cursor: pointer;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.login-code-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 11%;
|
||||||
|
border-top: 1px solid #e0e4e8;
|
||||||
|
|
||||||
|
.view {
|
||||||
|
width: 247px;
|
||||||
|
height: 20px;
|
||||||
|
margin-right: 10%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-top: 2px;
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: 'PingFang SC';
|
||||||
|
}
|
||||||
|
|
||||||
|
.url {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 10%;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
position: relative;
|
||||||
|
left: 60px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2px 7px;
|
||||||
|
line-height: 20px;
|
||||||
|
border: 1px solid #2f54eb;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: @screen-md-min) {
|
||||||
|
.container {
|
||||||
|
//background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center 110px;
|
||||||
|
background-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 32px 0 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -18,8 +18,9 @@
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
},
|
},
|
||||||
"types": ["ant-design-vue/typings/global"]
|
"types": ["ant-design-vue/typings/global"],
|
||||||
|
"suppressImplicitAnyIndexErrors": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }],
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import {defineConfig, loadEnv} from 'vite'
|
import {defineConfig, loadEnv} from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||||
import Components from 'unplugin-vue-components/vite'
|
import Components from 'unplugin-vue-components/vite'
|
||||||
import {AntDesignVueResolver} from 'unplugin-vue-components/resolvers'
|
import {AntDesignVueResolver} from 'unplugin-vue-components/resolvers'
|
||||||
import AutoImport from 'unplugin-auto-import/vite'
|
import AutoImport from 'unplugin-auto-import/vite'
|
||||||
import { createHtmlPlugin } from 'vite-plugin-html'
|
import { createHtmlPlugin } from 'vite-plugin-html'
|
||||||
import Config from './config/config'
|
import Config from './config/config'
|
||||||
import {VueAmapResolver} from '@vuemap/unplugin-resolver'
|
import {VueAmapResolver} from '@vuemap/unplugin-resolver'
|
||||||
|
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
|
||||||
|
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
|
||||||
|
@ -46,6 +48,7 @@ export default defineConfig(({ mode}) => {
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
|
vueJsx(),
|
||||||
Components({
|
Components({
|
||||||
resolvers: [AntDesignVueResolver({ importStyle: 'less' }), VueAmapResolver()],
|
resolvers: [AntDesignVueResolver({ importStyle: 'less' }), VueAmapResolver()],
|
||||||
directoryAsNamespace: true
|
directoryAsNamespace: true
|
||||||
|
@ -65,7 +68,8 @@ export default defineConfig(({ mode}) => {
|
||||||
favicon: `<link rel="icon" type="image/svg+xml" href="${Config.logo}" />`
|
favicon: `<link rel="icon" type="image/svg+xml" href="${Config.logo}" />`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
|
VueSetupExtend()
|
||||||
],
|
],
|
||||||
server: {
|
server: {
|
||||||
host:'0.0.0.0',
|
host:'0.0.0.0',
|
||||||
|
@ -74,9 +78,10 @@ export default defineConfig(({ mode}) => {
|
||||||
[env.VITE_APP_BASE_API]: {
|
[env.VITE_APP_BASE_API]: {
|
||||||
// target: 'http://192.168.33.22:8800',
|
// target: 'http://192.168.33.22:8800',
|
||||||
// target: 'http://192.168.32.244:8881',
|
// target: 'http://192.168.32.244:8881',
|
||||||
target: 'http://47.112.135.104:5096', // opcua
|
// target: 'http://47.112.135.104:5096', // opcua
|
||||||
|
target: 'http://47.108.63.174:8845', // 测试
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace('^'+env.VITE_APP_BASE_API, '')
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue