Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev

This commit is contained in:
xiongqian 2023-02-01 10:56:45 +08:00
commit e09eda6b41
56 changed files with 2005 additions and 747 deletions

View File

@ -1,7 +1,7 @@
import server from '@/utils/request';
// 更新全部菜单
export const updateMenus = (data: any) => server.path(`/menu/iot/_all`, data)
export const updateMenus = (data: any) => server.patch(`/menu/iot/_all`, data)
// 添加角色
export const addRole = (data: any) => server.post(`/role`, data)

View File

@ -121,4 +121,16 @@ export const edit = (data: any) => server.put(`/network/card/${data.id}`, data);
* id查看详情
* @param id
*/
export const queryDetail = (id: any) => server.get(`/network/card/${id}`);
export const queryDetail = (id: any) => server.get(`/network/card/${id}`);
/**
*
* @param data
*/
export const queryRechargeList = (data: any) => server.post(`/network/card/recharge/_log`, data)
/**
*
* @param data
*/
export const recharge = (data: any) => server.post(`/network/card/_recharge`, data)

View File

@ -36,7 +36,7 @@ export const undeploy = (id: string) =>
export const deploy = (id: string) =>
server.post(`/gateway/device/${id}/_startup`);
export const del = (id: string) => server.remove(`/gateway/device/${id}`);
export const remove = (id: string) => server.remove(`/gateway/device/${id}`);
export const getResourcesCurrent = () =>
server.get(`/network/resources/alive/_current`);

View File

@ -1,5 +1,5 @@
import { patch, post, get, remove } from '@/utils/request'
import { TemplateFormData } from '@/views/notice/Template/types'
import type { TemplateFormData } from '@/views/notice/Template/types'
export default {
// 列表

View File

@ -1,5 +1,5 @@
import { patch, post, get, remove } from '@/utils/request'
import { BindConfig } from '@/views/notice/Template/types'
import type { BindConfig } from '@/views/notice/Template/types'
export default {
// 列表

View File

@ -231,6 +231,13 @@ const handleClick = () => {
:deep(.card-item-content-title) {
cursor: pointer;
font-size: 16px;
font-weight: 700;
color: @primary-color;
width: calc(100% - 100px);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
:deep(.card-item-heard-name) {

View File

@ -21,6 +21,7 @@
import { ProLayout } from '@/components/Layout'
import DefaultSetting from '../../../config/config'
import { useMenuStore } from '@/store/menu'
import { clearMenuItem } from 'components/Layout/utils'
type StateType = {
collapsed: boolean
@ -39,7 +40,7 @@ const layoutConf = reactive({
siderWidth: DefaultSetting.layout.siderWidth,
logo: DefaultSetting.layout.logo,
title: DefaultSetting.layout.title,
menuData: menu.menus,
menuData: clearMenuItem(menu.siderMenus),
});
const state = reactive<StateType>({

View File

@ -32,7 +32,7 @@ export const HeaderView = defineComponent({
height: `${headerHeight.value}px`,
lineHeight: `${headerHeight.value}px`,
width: `100%`,
background: 'transparent'
}}
/>
<Layout.Header

View File

@ -14,10 +14,10 @@
flex-direction: column;
}
.children-full-height {
> :nth-child(1) {
min-height: 100%;
}
}
//.children-full-height {
// > :nth-child(1) {
// min-height: 100%;
// }
//}
}
}

View File

@ -13,4 +13,8 @@
flex-direction: column;
height: 100%;
}
.ant-menu-inline {
background: transparent;
}
}

View File

@ -286,6 +286,7 @@ handleItems()
.JSearch-warp {
padding: 24px;
background-color: #fff;
margin-bottom: 24px;
.JSearch-content {
display: flex;

View File

@ -10,17 +10,23 @@
.jtable-body-header-right {
display: flex;
gap: 8px;
.jtable-setting-item {
color: rgba(0, 0, 0, 0.75);
font-size: 16px;
cursor: pointer;
align-items: center;
.jtable-body-header-right-button {
display: flex;
margin-left: 10px;
gap: 8px;
.jtable-setting-item {
color: rgba(0, 0, 0, 0.75);
font-size: 16px;
cursor: pointer;
&:hover {
color: @primary-color-hover;
}
&:hover {
color: @primary-color-hover;
}
&.active {
color: @primary-color-active;
&.active {
color: @primary-color-active;
}
}
}
}

View File

@ -73,6 +73,7 @@ const JTable = defineComponent<JTableProps>({
slots: [
'headerTitle', // 顶部左边插槽
'card', // 卡片内容
'rightExtraRender'
],
emits: [
'modelChange', // 切换卡片和表格
@ -254,16 +255,22 @@ const JTable = defineComponent<JTableProps>({
{slots.headerTitle && slots.headerTitle()}
</div>
<div class={styles["jtable-body-header-right"]}>
<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>
{/* 顶部右边插槽 */}
{slots.rightExtraRender && slots.rightExtraRender()}
{
!props.model && <div class={styles["jtable-body-header-right-button"]}>
<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>
</div>
}
</div>
</div>
{/* content */}

View File

@ -1,17 +1,14 @@
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'
import components from './components'
import router from './router'
import './style.less'
import 'ant-design-vue/es/notification/style/css';
import Antd from 'ant-design-vue/es'
const app = createApp(App)
app.use(store)
app.use(router)
app.use(components)
app.use(Antd)
app.mount('#app')
.use(router)
.use(components)
.mount('#app')

View File

@ -3,6 +3,8 @@ import menus, { LoginPath } from './menu'
import { cleanToken, getToken } from '@/utils/comm'
import { useUserInfo } from '@/store/userInfo'
import { useSystem } from '@/store/system'
import NotFindPage from '@/views/404.vue'
import { useMenuStore } from 'store/menu'
const router = createRouter({
history: createWebHashHistory(),
@ -27,22 +29,23 @@ router.beforeEach((to, from, next) => {
} else {
const userInfo = useUserInfo()
const system = useSystem()
const menu = useMenuStore()
if (!userInfo.userInfos.username) {
if (!menu.menuData.length) {
userInfo.getUserInfo().then(() => {
system.getSystemVersion().then((menuData: any[]) => {
menuData.forEach(r => {
router.addRoute('main', r)
router.addRoute('base', r)
})
const redirect = decodeURIComponent((from.query.redirect as string) || to.path)
if(to.path === redirect) {
next({ ...to, replace: true })
} else {
next({ path: redirect })
}
router.addRoute('base',{
path: '/:pathMatch(.*)',
name: 'error',
component: () => NotFindPage
})
next({ ...to, replace: true })
})
}).catch(() => {
console.log('userInfo', userInfo)
cleanToken()
next({ path: LoginPath })
})

View File

@ -1,24 +1,7 @@
export const LoginPath = '/login'
export default [
// {
// path: '/',
// redirect: LoginPath
// },
// {
// path: '/init',
// component: () => import('@/view/InitPage.vue')
// },
// {
// path: LoginPath,
// name: 'login',
// component: () => import('@/view/Login/index.vue')
// },
// {
// path: '/initsetting',
// component: () => import('@/view/Login/initSet.vue')
// }
{ path: '/*', redirect: '/'},
// start: 测试用, 可删除
{
path: '/login',
@ -32,10 +15,6 @@ export default [
path: '/account/center/bind',
component: () => import('@/views/account/Center/bind/index.vue')
},
{
path: '/iot/home',
component: () => import('@/views/home/index.vue')
},
{
path: '/table',
component: () => import('@/views/demo/table/index.vue')
@ -48,129 +27,13 @@ export default [
path: '/search',
component: () => import('@/views/demo/Search.vue')
},
{
path: '/notice/Config',
component: () => import('@/views/notice/Config/index.vue')
},
{
path: '/notice/Config/detail/:id',
component: () => import('@/views/notice/Config/Detail/index.vue')
},
{
path: '/notice/Template',
component: () => import('@/views/notice/Template/index.vue')
},
{
path: '/notice/Template/detail/:id',
component: () => import('@/views/notice/Template/Detail/index.vue')
},
// end: 测试用, 可删除
// 设备管理
{
path: '/device/instance',
component: () => import('@/views/device/Instance/index.vue')
},
{
// path: '/device/Instance/detail/:id',
// component: () => import('@/views/device/Instance/detail.vue')
path: '/device/instance/detail/:id',
component: () => import('@/views/device/Instance/Detail/index.vue')
},
// 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/:type/:id',
component: () => import('@/views/link/Certificate/Detail/index.vue')
},
{
path: '/link/accessConfig',
component: () => import('@/views/link/AccessConfig/index.vue')
},
{
path: '/link/accessConfig/detail/add',
component: () => import('@/views/link/AccessConfig/Detail/index.vue')
},
// system 系统管理
{
path:'/system/Basis',
component: ()=>import('@/views/system/Basis/index.vue')
},
{
path:'/system/api',
component: ()=>import('@/views/system/apiPage/index.vue')
},
{
path:'/system/Role',
component: ()=>import('@/views/system/Role/index.vue')
},
{
path:'/system/Role/detail/:id',
component: ()=>import('@/views/system/Role/Detail/index.vue')
},
{
path:'/system/Permission',
component: ()=>import('@/views/system/Permission/index.vue')
},
{
path:'/system/Menu',
component: ()=>import('@/views/system/Menu/index.vue')
},
{
path:'/system/Menu/detail/:id',
component: ()=>import('@/views/system/Menu/Detail/index.vue')
},
// 初始化
{
path: '/init-home',
component: () => import('@/views/init-home/index.vue')
},
// 物联卡 iot-card
{
path: '/iot-card/Home',
component: () => import('@/views/iot-card/Home/index.vue')
},
{
path: '/iot-card/Dashboard',
component: () => import('@/views/iot-card/Dashboard/index.vue')
},
{
path: '/iot-card/CardManagement',
component: () => import('@/views/iot-card/CardManagement/index.vue')
},
{
path: '/iot-card/CardManagement/Detail',
component: () => import('@/views/iot-card/CardManagement/Detail/index.vue')
},
{
path: '/iot-card/Recharge',
component: () => import('@/views/iot-card/Recharge/index.vue')
},
// 北向输出
{
path: '/northbound/DuerOS',
component: () => import('@/views/northbound/DuerOS/index.vue')
},
{
path: '/northbound/AliCloud',
component: () => import('@/views/northbound/AliCloud/index.vue')
},
// 产品分类
{
path: '/iot/device/Category',
component: () => import('@/views/device/Category/index.vue')
} ,
// 产品
{
path: '/iot/device/Product',
component: () => import('@/views/device/Product/index.vue')
}
]

View File

@ -1,11 +1,14 @@
import { defineStore } from "pinia";
import { queryOwnThree } from '@/api/system/menu'
import { filterAsnycRouter } from '@/utils/menu'
import { cloneDeep } from 'lodash-es'
export const useMenuStore = defineStore({
id: 'menu',
state: () => ({
menus: {},
menuData: [],
siderMenus: [],
menusKey: []
}),
getters: {
@ -51,7 +54,8 @@ export const useMenuStore = defineStore({
];
const resp = await queryOwnThree({ paging: false, terms: params })
if (resp.success) {
const menus = filterAsnycRouter(resp.result)
const silderMenus = filterAsnycRouter(cloneDeep(resp.result))
const menus = filterAsnycRouter(cloneDeep(resp.result))
menus.push({
path: '/',
redirect: menus[0]?.path,
@ -59,7 +63,9 @@ export const useMenuStore = defineStore({
hideInMenu: true
}
})
this.menus = menus
this.menuData = menus
this.siderMenus = silderMenus
console.log('silderMenus', silderMenus)
res(menus)
}
})

View File

@ -1,11 +1,99 @@
const pagesComponent = import.meta.glob('../views/system/**/*.vue', { eager: true });
const pagesComponent = import.meta.glob('../views/**/*.vue', { eager: true });
import { BlankLayoutPage, BasicLayoutPage } from 'components/Layout'
type ExtraRouteItem = {
code: string
name: string
url?: string
}
/**
*
*/
export type PermissionInfo = {
permission: string;
actions: string[];
};
/**
*
*/
export type MenuButtonInfo = {
id: string;
name: string;
permissions: PermissionInfo;
createTime: number;
describe?: string;
options: Record<string, any>;
};
export type MenuItem = {
id: string;
/**
*
*/
name: string;
/**
*
*/
code: string;
/**
*
*/
application: string;
/**
*
*/
describe: string;
/**
* url
*/
url: string;
/**
*
*/
icon: string;
/**
* , 01
*/
status: number;
/**
*
*/
permissions: PermissionInfo[];
/**
*
*/
buttons: MenuButtonInfo[];
/**
*
*/
options: Record<string, any>;
/**
* ID
*/
parentId: string;
/**
*
*/
path: string;
/**
*
*/
sortIndex: number;
/**
*
*/
level: number;
createTime: number;
redirect?: string;
children?: MenuItem[];
accessSupport?: { text: string; value: string };
appId?: string; //应用id
isShow?: boolean;
meta?: {
title?: string
icon?: string
[key: string]: any
},
component?: any
};
// 额外子级路由
const extraRouteObj = {
'media/Cascade': {
@ -59,53 +147,83 @@ const extraRouteObj = {
const resolveComponent = (name: any) => {
// TODO 暂时用system进行测试
const importPage = pagesComponent[`../views/${name}/index.vue`];
// if (!importPage) {
// throw new Error(`Unknown page ${name}. Is it located under Pages with a .vue extension?`);
// }
if (!importPage) {
console.warn(`Unknown page ${name}. Is it located under Pages with a .vue extension?`)
}
//@ts-ignore
return !importPage ? BlankLayoutPage : importPage.default
// return importPage.default
return !!importPage ? importPage.default : undefined
}
const findChildrenRoute = (code: string, url: string): ExtraRouteItem[] => {
const findChildrenRoute = (code: string, url: string): MenuItem[] => {
if (extraRouteObj[code]) {
return extraRouteObj[code].children.map((route: ExtraRouteItem) => {
return extraRouteObj[code].children.map((route: MenuItem) => {
return {
url: `${url}/${route.code}`,
code: route.code,
name: route.name
name: route.name,
isShow: false
}
})
}
return []
}
const findDetailRouteItem = (code: string, url: string): Partial<MenuItem> | null => {
const detailComponent = resolveComponent(`${code}/Detail`)
if (detailComponent) {
return {
url: `${url}/Detail/:id`,
component: detailComponent,
name: '详情信息',
isShow: false
}
}
return null
}
const findDetailRoutes = (routes: any[]): any[] => {
const newRoutes: any[] = []
routes.forEach((route: any) => {
newRoutes.push(route)
const detail = findDetailRouteItem(route.code, route.url)
if (detail) {
newRoutes.push(detail)
}
})
return newRoutes
}
export function filterAsnycRouter(asyncRouterMap: any, parentCode = '', level = 1) {
return asyncRouterMap.map((route: any) => {
route.path = `${route.url}`
route.meta = {
icon: route.icon,
title: route.name
title: route.name,
hideInMenu: route.isShow === false
}
// 查看是否有隐藏子路由
route.children = route.children && route.children.length ? [...route.children, ...findChildrenRoute(route.code, route.url)] : findChildrenRoute(route.code, route.url)
// TODO 查看是否具有详情页
// route.children = [...route.children, ]
const extraChildren = findChildrenRoute(route.code, route.url)
route.children = route.children && route.children.length ? [...route.children, ...extraChildren] : extraChildren
route.children = findDetailRoutes(route.children)
if (route.children && route.children.length) {
route.component = () => level === 1 ? BasicLayoutPage : BlankLayoutPage
// TODO 查看是否具有详情页
route.children = filterAsnycRouter(route.children, `${parentCode}/${route.code}`, level + 1)
route.redirect = route.children[0].url
const showChildren = route.children.some((r: any) => !r.meta.hideInMenu)
if (showChildren) {
route.component = () => level === 1 ? BasicLayoutPage : BlankLayoutPage
route.redirect = route.children[0].url
} else {
route.component = resolveComponent(route.code) || BlankLayoutPage;
}
} else {
route.component = resolveComponent(route.code);
console.log(route.code)
route.component = route.component || resolveComponent(route.code) || BlankLayoutPage;
}
console.log(route.code, route)
delete route.name
return route
})
}

14
src/views/404.vue Normal file
View File

@ -0,0 +1,14 @@
<template>
<a-result status="404" title="404" sub-title="Sorry, the page you visited does not exist.">
</a-result>
</template>
<script>
export default {
name: '404.vue'
}
</script>
<style scoped>
</style>

View File

@ -1,221 +1,234 @@
<template>
<JTable
ref="instanceRef"
:columns="columns"
:request="query"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
:rowSelection="{
selectedRowKeys: _selectedRowKeys,
onChange: onSelectChange,
}"
@cancelSelect="cancelSelect"
:params="params"
>
<template #headerTitle>
<a-space>
<a-button type="primary" @click="handleAdd">新增</a-button>
<a-dropdown>
<a-button>批量操作 <AIcon type="DownOutlined" /></a-button>
<template #overlay>
<a-menu>
<a-menu-item>
<a-button @click="exportVisible = true"
><AIcon
type="ExportOutlined"
/></a-button
>
</a-menu-item>
<a-menu-item>
<a-button @click="importVisible = true"
><AIcon
type="ImportOutlined"
/></a-button
>
</a-menu-item>
<a-menu-item>
<a-popconfirm
@confirm="activeAllDevice"
title="确认激活全部设备?"
>
<a-button type="primary" ghost
<page-container>
<Search :columns="columns" target="device-instance" />
<JTable
ref="instanceRef"
:columns="columns"
:request="query"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
:rowSelection="{
selectedRowKeys: _selectedRowKeys,
onChange: onSelectChange,
}"
@cancelSelect="cancelSelect"
:params="params"
>
<template #headerTitle>
<a-space>
<a-button type="primary" @click="handleAdd">新增</a-button>
<a-dropdown>
<a-button
>批量操作 <AIcon type="DownOutlined"
/></a-button>
<template #overlay>
<a-menu>
<a-menu-item>
<a-button @click="exportVisible = true"
><AIcon
type="CheckCircleOutlined"
/></a-button
type="ExportOutlined"
/></a-button
>
</a-popconfirm>
</a-menu-item>
<a-menu-item>
<a-button
@click="syncDeviceStatus"
type="primary"
><AIcon
type="SyncOutlined"
/></a-button
>
</a-menu-item>
<a-menu-item v-if="_selectedRowKeys.length">
<a-popconfirm
@confirm="delSelectedDevice"
title="已启用的设备无法删除,确认删除选中的禁用状态设备?"
>
<a-button type="primary" danger
</a-menu-item>
<a-menu-item>
<a-button @click="importVisible = true"
><AIcon
type="DeleteOutlined"
/></a-button
type="ImportOutlined"
/></a-button
>
</a-popconfirm>
</a-menu-item>
<a-menu-item
v-if="_selectedRowKeys.length"
title="确认激活选中设备?"
>
<a-popconfirm @confirm="activeSelectedDevice">
<a-button type="primary"
</a-menu-item>
<a-menu-item>
<a-popconfirm
@confirm="activeAllDevice"
title="确认激活全部设备?"
>
<a-button type="primary" ghost
><AIcon
type="CheckCircleOutlined"
/></a-button
>
</a-popconfirm>
</a-menu-item>
<a-menu-item>
<a-button
@click="syncDeviceStatus"
type="primary"
><AIcon
type="CheckOutlined"
/></a-button
type="SyncOutlined"
/></a-button
>
</a-popconfirm>
</a-menu-item>
<a-menu-item v-if="_selectedRowKeys.length">
<a-popconfirm
@confirm="disabledSelectedDevice"
title="确认禁用选中设备?"
</a-menu-item>
<a-menu-item v-if="_selectedRowKeys.length">
<a-popconfirm
@confirm="delSelectedDevice"
title="已启用的设备无法删除,确认删除选中的禁用状态设备?"
>
<a-button type="primary" danger
><AIcon
type="DeleteOutlined"
/></a-button
>
</a-popconfirm>
</a-menu-item>
<a-menu-item
v-if="_selectedRowKeys.length"
title="确认激活选中设备?"
>
<a-button type="primary" danger
><AIcon
type="StopOutlined"
/></a-button
<a-popconfirm
@confirm="activeSelectedDevice"
>
</a-popconfirm>
</a-menu-item>
</a-menu>
<a-button type="primary"
><AIcon
type="CheckOutlined"
/></a-button
>
</a-popconfirm>
</a-menu-item>
<a-menu-item v-if="_selectedRowKeys.length">
<a-popconfirm
@confirm="disabledSelectedDevice"
title="确认禁用选中设备?"
>
<a-button type="primary" danger
><AIcon
type="StopOutlined"
/></a-button
>
</a-popconfirm>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-space>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
@click="handleClick"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:active="_selectedRowKeys.includes(slotProps.id)"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
online: 'success',
offline: 'error',
notActive: 'warning',
}"
>
<template #img>
<slot name="img">
<img
:src="
getImage('/device/instance/device-card.png')
"
/>
</slot>
</template>
</a-dropdown>
</a-space>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
@click="handleClick"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:active="_selectedRowKeys.includes(slotProps.id)"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
online: 'success',
offline: 'error',
notActive: 'warning',
}"
>
<template #img>
<slot name="img">
<img
:src="getImage('/device/instance/device-card.png')"
/>
</slot>
</template>
<template #content>
<h3
class="card-item-content-title"
@click.stop="handleView(slotProps.id)"
>
{{ slotProps.name }}
</h3>
<a-row>
<a-col :span="12">
<div class="card-item-content-text">设备类型</div>
<div>{{ slotProps.deviceType?.text }}</div>
</a-col>
<a-col :span="12">
<div class="card-item-content-text">产品名称</div>
<div>{{ slotProps.productName }}</div>
</a-col>
</a-row>
</template>
<template #actions="item">
<template #content>
<h3
class="card-item-content-title"
@click.stop="handleView(slotProps.id)"
>
{{ slotProps.name }}
</h3>
<a-row>
<a-col :span="12">
<div class="card-item-content-text">
设备类型
</div>
<div>{{ slotProps.deviceType?.text }}</div>
</a-col>
<a-col :span="12">
<div class="card-item-content-text">
产品名称
</div>
<div>{{ slotProps.productName }}</div>
</a-col>
</a-row>
</template>
<template #actions="item">
<a-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
>
<a-popconfirm
v-if="item.popConfirm"
v-bind="item.popConfirm"
:disabled="item.disabled"
>
<a-button :disabled="item.disabled">
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</a-button>
</a-popconfirm>
<template v-else>
<a-button
:disabled="item.disabled"
@click="item.onClick"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</a-button>
</template>
</a-tooltip>
</template>
</CardBox>
</template>
<template #state="slotProps">
<a-badge
:text="slotProps.state?.text"
:status="statusMap.get(slotProps.state?.value)"
/>
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm
v-if="item.popConfirm"
v-bind="item.popConfirm"
:disabled="item.disabled"
v-if="i.popConfirm"
v-bind="i.popConfirm"
:disabled="i.disabled"
>
<a-button :disabled="item.disabled">
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</a-button>
</a-popconfirm>
<template v-else>
<a-button
:disabled="item.disabled"
@click="item.onClick"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</a-button>
</template>
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-popconfirm>
<a-button
style="padding: 0"
type="link"
v-else
@click="i.onClick && i.onClick(slotProps)"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip>
</template>
</CardBox>
</template>
<template #state="slotProps">
<a-badge
:text="slotProps.state?.text"
:status="statusMap.get(slotProps.state?.value)"
/>
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm
v-if="i.popConfirm"
v-bind="i.popConfirm"
:disabled="i.disabled"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-popconfirm>
<a-button
style="padding: 0"
type="link"
v-else
@click="i.onClick && i.onClick(slotProps)"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip>
</a-space>
</template>
</JTable>
</a-space>
</template>
</JTable>
</page-container>
<Import v-if="importVisible" @close="importVisible = false" />
<Export
v-if="exportVisible"
@ -228,7 +241,12 @@
:api="api"
:type="type"
/>
<Save v-if="visible" :data="current" @close="visible = false" @save="saveBtn" />
<Save
v-if="visible"
:data="current"
@close="visible = false"
@save="saveBtn"
/>
</template>
<script setup lang="ts">
@ -367,8 +385,8 @@ const handleAdd = () => {
* 查看
*/
const handleView = (id: string) => {
router.push('/device/instance/detail/' + id)
}
router.push('/device/instance/detail/' + id);
};
const getActions = (
data: Partial<Record<string, any>>,
@ -519,10 +537,10 @@ const disabledSelectedDevice = async () => {
_selectedRowKeys.value = [];
instanceRef.value?.reload();
}
}
};
const saveBtn = () => {
visible.value = false
instanceRef.value?.reload()
}
visible.value = false;
instanceRef.value?.reload();
};
</script>

View File

@ -33,7 +33,7 @@ const BaseColumns: JColumnProps[] = [
const EventColumns: JColumnProps[] = BaseColumns.concat([
{
title: '事件级别',
dataIndex: 'expands',
dataIndex: 'level',
scopedSlots: true,
},
]);
@ -59,12 +59,12 @@ const PropertyColumns: JColumnProps[] = BaseColumns.concat([
},
{
title: '属性来源',
dataIndex: 'expands',
dataIndex: 'source',
scopedSlots: true,
},
{
title: '读写类型',
dataIndex: 'expands',
dataIndex: 'type',
scopedSlots: true,
},
]);
@ -77,7 +77,7 @@ const TagColumns: JColumnProps[] = BaseColumns.concat([
},
{
title: '读写类型',
dataIndex: 'expands',
dataIndex: 'type',
scopedSlots: true,
},
]);

View File

@ -1,5 +1,5 @@
<template>
<JTable :loading="loading" :data-source="data" size="small" :columns="columns" row-key="id">
<JTable :loading="loading" :data-source="data" size="small" :columns="columns" row-key="id" model="TABLE">
<template #headerTitle>
<a-input-search v-model:value="searchValue" placeholder="请输入名称" @search="handleSearch"></a-input-search>
<PermissionButton :has-permission="permission" key="add" @click="handleAddClick"
@ -11,11 +11,24 @@
</template>
新增
</PermissionButton>
<Edit
v-if="metadataStore.model.edit"
:type="target"
:tabs="type"
></Edit>
<Edit v-if="metadataStore.model.edit" :type="target" :tabs="type"></Edit>
</template>
<template #level="slotProps">
{{ levelMap[slotProps.expands?.level] || '-' }}
</template>
<template #async="slotProps">
{{ slotProps.async ? '是' : '否' }}
</template>
<template #valueType="slotProps">
{{ slotProps.valueType?.type }}
</template>
<template #source="slotProps">
{{ sourceMap[slotProps.expands?.source] }}
</template>
<template #type="slotProps">
<a-tag v-for="item in (slotProps.expands?.type || [])" :key="item">
{{ expandsType[item] }}
</a-tag>
</template>
</JTable>
</template>
@ -42,6 +55,21 @@ const productStore = useProductStore()
const loading = ref(false)
const data = ref<MetadataItem[]>([])
const { type, target = 'product' } = props
const levelMap = ref({
ordinary: '普通',
warn: '警告',
urgent: '紧急',
})
const sourceMap = ref({
device: '设备',
manual: '手动',
rule: '规则',
});
const expandsType = ref({
read: '读',
write: '写',
report: '上报',
});
const actions: JColumnProps[] = [
{
title: '操作',
@ -97,4 +125,5 @@ const operateLimits = (action: 'add' | 'updata', types: MetadataType) => {
};
</script>
<style scoped lang="less">
</style>

View File

@ -19,7 +19,7 @@
<a-tab-pane v-for="item in codecs" :key="item.id" :tab="item.name">
<div class="cat-panel">
<!-- TODO 代码编辑器 -->
{{ value }}
<MonacoEditor v-model="value" theme="vs" style="height: 100%"></MonacoEditor>
</div>
</a-tab-pane>
</a-tabs>

View File

@ -36,7 +36,7 @@
<template #addonAfter>
<a-upload v-model:file-list="fileList" :before-upload="beforeUpload" accept=".json"
:show-upload-list="false" :action="FILE_UPLOAD" @change="fileChange"
:headers="{ 'X-Access-Token': token }">
:headers="{ 'X-Access-Token': getToken()}">
<upload-outlined class="upload-button"/>
<!-- <button id="uploadFile" style="display: none;"></button> -->
</a-upload>
@ -45,7 +45,7 @@
</a-form-item>
<a-form-item label="物模型" v-bind="validateInfos.import" v-if="formModel.metadataType === 'script'">
<!-- TODO代码编辑器 -->
<a-textarea v-model:value="formModel.import"></a-textarea>
<MonacoEditor v-model="formModel.import" theme="vs" style="height: 300px"></MonacoEditor>
</a-form-item>
</a-form>
</a-modal>
@ -64,8 +64,8 @@ import { useInstanceStore } from '@/store/instance'
import { useProductStore } from '@/store/product';
import { UploadOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { FILE_UPLOAD } from '@/api/comm';
import { LocalStore } from '@/utils/comm';
import { TOKEN_KEY } from '@/utils/variable';
import { LocalStore, getToken } from '@/utils/comm';
import MonacoEditor from '@/components/MonacoEditor/index.vue'
const route = useRoute()
const instanceStore = useInstanceStore()
@ -147,7 +147,6 @@ const onSubmit = () => {
})
}
const fileList = ref<UploadFile[]>([])
const token = ref(LocalStore.get(TOKEN_KEY));
const productList = ref<DefaultOptionType[]>([])

View File

@ -32,16 +32,16 @@
</template>
<a-tab-pane tab="属性定义" key="properties">
<BaseMetadata target={props.type} type="properties" :permission="permission" />
<BaseMetadata :target="type" type="properties" :permission="permission" />
</a-tab-pane>
<a-tab-pane tab="功能定义" key="functions">
<BaseMetadata target={props.type} type="functions" :permission="permission" />
<BaseMetadata :target="type" type="functions" :permission="permission" />
</a-tab-pane>
<a-tab-pane tab="事件定义" key="events">
<BaseMetadata target={props.type} type="events" :permission="permission" />
<BaseMetadata :target="type" type="events" :permission="permission" />
</a-tab-pane>
<a-tab-pane tab="标签定义" key="tags">
<BaseMetadata target={props.type} type="tags" :permission="permission" />
<BaseMetadata :target="type" type="tags" :permission="permission" />
</a-tab-pane>
</a-tabs>
<Import v-model:visible="visible" :type="type" @close="visible = false" />
@ -58,6 +58,7 @@ import { SystemConst } from '@/utils/consts'
import { useInstanceStore } from '@/store/instance'
import Import from './Import/index.vue'
import Cat from './Cat/index.vue'
import BaseMetadata from './Base/index.vue'
const route = useRoute()
const instanceStore = useInstanceStore()

View File

@ -19,6 +19,7 @@
ref="bindDeviceRef"
:columns="columns"
:request="queryUnbounded"
model="TABLE"
:defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }],
}"

View File

@ -0,0 +1,63 @@
<template>
<a-modal
:maskClosable="false"
width="1000px"
:visible="true"
title="详情"
okText="确定"
cancelText="取消"
@ok="handleCancel"
@cancel="handleCancel"
>
<div style="margin-top: 10px">
<a-descriptions
:column="2"
bordered
:contentStyle="{ minWidth: '300px' }"
:labelStyle="{ minWidth: '120px' }"
>
<a-descriptions-item label="充值金额">{{
data.chargeMoney
}}</a-descriptions-item>
<a-descriptions-item label="账户id">{{
data?.rechargeId
}}</a-descriptions-item>
<a-descriptions-item label="平台对接">{{
data.configName
}}</a-descriptions-item>
<a-descriptions-item label="订单号">{{
data.orderNumber
}}</a-descriptions-item>
<a-descriptions-item label="支付方式">{{
data.paymentType
}}</a-descriptions-item>
<a-descriptions-item label="支付URL">
<div style="height: 100px; overflow: auto">
{{ data.url ? data.url : '' }}
</div>
</a-descriptions-item>
<a-descriptions-item label="订单时间">{{
data.createTime
? moment(data.createTime).format('YYYY-MM-DD HH:mm:ss')
: '-'
}}</a-descriptions-item>
</a-descriptions>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import moment from 'moment';
const emit = defineEmits(['close']);
const props = defineProps({
data: {
type: Object,
default: () => {},
},
});
const handleCancel = () => {
emit('close');
};
</script>

View File

@ -0,0 +1,191 @@
<template>
<a-modal
:maskClosable="false"
width="600px"
:visible="true"
title="充值"
@ok="handleOk"
@cancel="handleCancel"
:confirmLoading="btnLoading"
>
<div>
<div class="modal-info">
<AIcon
type="ExclamationCircleOutlined"
style="margin-right: 6px"
/>OneLink
</div>
<a-form
layout="vertical"
ref="formRef"
:rules="rules"
:model="modelRef"
>
<a-form-item label="平台对接" name="configId">
<a-select
v-model:value="modelRef.configId"
:options="configList"
allowClear
show-search
style="width: 100%"
placeholder="请选择平台对接"
>
</a-select>
</a-form-item>
<a-form-item label="账户id" name="rechargeId">
<a-input
v-model:value="modelRef.rechargeId"
placeholder="请输入账户id"
/>
</a-form-item>
<a-form-item label="充值金额" name="chargeMoney">
<a-input-number
allowClear
:precision="2"
style="width: 100%"
v-model:value="modelRef.chargeMoney"
:min="1"
:max="500"
placeholder="请输入1~500之间的金额"
/>
</a-form-item>
<a-form-item label="支付方式" name="paymentType">
<a-select
allowClear
:options="PaymentMethod"
v-model:value="modelRef.paymentType"
placeholder="请选择支付方式"
>
</a-select>
</a-form-item>
</a-form>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { queryPlatformNoPage, recharge } from '@/api/iot-card/cardManagement';
import { message } from 'ant-design-vue';
import { PaymentMethod } from '@/views/iot-card/data';
const emit = defineEmits(['change']);
const btnLoading = ref<boolean>(false);
const configList = ref<Record<string, any>[]>([]);
const formRef = ref();
const modelRef = reactive({
configId: undefined,
rechargeId: '',
chargeMoney: undefined,
paymentType: undefined,
});
const rules = {
configId: [
{
required: true,
message: '请选择平台对接',
},
{
max: 64,
message: '最多输入64个字符',
},
],
rechargeId: [
{
required: true,
message: '请输入账户id',
},
{
max: 64,
message: '最多输入64个字符',
},
],
chargeMoney: [
{
required: true,
message: '请输入充值金额',
},
// {
// min: 1,
// max: 500,
// message: '1~500',
// },
],
paymentType: [
{
required: true,
message: '请选择支付方式',
},
],
};
const queryProvidersList = () => {
queryPlatformNoPage({
paging: false,
terms: [
{
terms: [
{
column: 'operatorName',
termType: 'eq',
value: 'onelink',
},
{
column: 'state',
termType: 'eq',
value: 'enabled',
type: 'and',
},
],
},
],
}).then((res: any) => {
configList.value = res.result.map((item: any) => ({
label: item.name,
value: item.id,
}));
});
};
const handleCancel = () => {
emit('change');
formRef.value.resetFields();
};
const handleOk = () => {
formRef.value
.validate()
.then(async () => {
btnLoading.value = true;
const resp: any = await recharge(toRaw(modelRef));
btnLoading.value = false;
if (resp.status === 200) {
if (resp.result === '失败') {
message.error('缴费失败');
} else {
window.open(resp.result);
}
emit('change');
formRef.value.resetFields();
}
})
.catch((err: any) => {
console.log('error', err);
});
};
queryProvidersList();
</script>
<style scoped lang="less">
.modal-info {
color: rgba(0, 0, 0, 0.55);
background-color: #f6f6f6;
padding: 6px 12px;
margin-bottom: 12px;
}
</style>

View File

@ -1,8 +1,184 @@
<!-- 充值管理 -->
<template>
<div class="page-container">充值管理</div>
<div class="page-container">
<Search
:columns="columns"
target="iot-card-management-search"
@search="handleSearch"
/>
<JTable
ref="rechargeRef"
:columns="columns"
:request="queryRechargeList"
model="TABLE"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
:params="params"
>
<template #headerTitle>
<a-space>
<a-button type="primary" @click="visible = true">
充值
</a-button>
<div class="tips-text">
<span style="margin-right: 8px; font-size: 16px">
<AIcon type="ExclamationCircleOutlined"></AIcon>
</span>
本平台仅提供充值入口具体充值结果需以运营商的充值结果为准
</div>
</a-space>
</template>
<template #createTime="slotProps">
{{
slotProps.createTime
? moment(slotProps.createTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
v-for="i in getActions(slotProps)"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm v-if="i.popConfirm" v-bind="i.popConfirm">
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-popconfirm>
<a-button
style="padding: 0"
type="link"
v-else
@click="i.onClick && i.onClick(slotProps)"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip>
</a-space>
</template>
</JTable>
<!-- 充值 -->
<Save v-if="visible" @change="saveChange" />
<Detail v-if="detailVisible" :data="current" @close="close" />
</div>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
import moment from 'moment';
import type { ActionsType } from '@/components/Table';
import { queryRechargeList } from '@/api/iot-card/cardManagement';
import Save from './Save.vue';
import Detail from './Detail.vue';
<style scoped lang="less"></style>
const rechargeRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({});
const visible = ref<boolean>(false);
const detailVisible = ref<boolean>(false);
const current = ref<Record<string, any>>({});
const columns = [
{
title: '充值金额',
dataIndex: 'chargeMoney',
key: 'chargeMoney',
ellipsis: true,
search: {
type: 'string',
},
},
{
title: '支付方式',
dataIndex: 'paymentType',
key: 'paymentType',
search: {
type: 'string',
},
},
{
title: '订单号',
dataIndex: 'orderNumber',
key: 'orderNumber',
ellipsis: true,
},
{
title: '支付URL',
dataIndex: 'url',
key: 'url',
},
{
title: '订单时间',
dataIndex: 'createTime',
key: 'createTime',
scopedSlots: true,
search: {
type: 'date',
},
},
{
title: '操作',
key: 'action',
fixed: 'right',
scopedSlots: true,
},
];
const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
if (!data) return [];
return [
{
key: 'view',
text: '查看',
tooltip: {
title: '查看',
},
icon: 'EyeOutlined',
onClick: () => {
detailVisible.value = true;
current.value = data;
},
},
];
};
const handleSearch = (params: any) => {
console.log(params);
params.value = params;
};
/**
* 充值关闭弹窗
* @param val 加载表格
*/
const saveChange = (val: any) => {
visible.value = false;
if (val) {
rechargeRef.value?.reload();
}
};
/**
* 查看详情弹窗关闭
*/
const close = () => {
detailVisible.value = false;
};
</script>
<style scoped lang="less">
.tips-text {
padding-left: 24px;
background: #fff;
font-size: 14px;
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<a-spin :spinning="loading">
<a-card :bordered="false">
<div v-if="type">
<div v-if="type && modeType === 'add'">
<Provider
@onClick="goProviders"
:dataSource="dataSource"
@ -14,9 +14,21 @@
:provider="provider"
:data="data"
/>
<Media v-if="showType === 'media'" :provider="provider" />
<Channel v-if="showType === 'channel'" :provider="provider" />
<Edge v-if="showType === 'edge'" :provider="provider" />
<Media
v-if="showType === 'media'"
:provider="provider"
:data="data"
/>
<Channel
v-if="showType === 'channel'"
:provider="provider"
:data="data"
/>
<Edge
v-if="showType === 'edge'"
:provider="provider"
:data="data"
/>
<Cloud
v-if="showType === 'cloud'"
:provider="provider"
@ -37,10 +49,10 @@ import Channel from '../components/Channel/index.vue';
import Edge from '../components/Edge/index.vue';
import Cloud from '../components/Cloud/index.vue';
// const router = useRouter();
const route = useRoute();
const id = route.query.id;
const modeType = route.params.type as string;
const id = route.params.id as string;
const dataSource = ref([]);
const type = ref(false);
@ -53,7 +65,6 @@ const goProviders = (param: object) => {
showType.value = param.type;
provider.value = param;
type.value = false;
console.log(1123, showType.value, param);
};
const goBack = () => {
@ -61,80 +72,77 @@ const goBack = () => {
type.value = true;
};
const getTypeList = (result: any[]) => {
const list = [];
const media: any[] = [];
const network: any[] = [];
const cloud: any[] = [];
const channel: any[] = [];
const edge: any[] = [];
result.map((item) => {
if (item.id === 'fixed-media' || item.id === 'gb28181-2016') {
item.type = 'media';
media.push(item);
} else if (item.id === 'OneNet' || item.id === 'Ctwing') {
item.type = 'cloud';
cloud.push(item);
} else if (item.id === 'modbus-tcp' || item.id === 'opc-ua') {
item.type = 'channel';
channel.push(item);
} else if (
item.id === 'official-edge-gateway' ||
item.id === 'edge-child-device'
) {
item.type = 'edge';
edge.push(item);
} else {
item.type = 'network';
network.push(item);
}
});
network.length &&
list.push({
list: [...network],
title: '自定义设备接入',
});
media.length &&
list.push({
list: [...media],
title: '视频类设备接入',
});
cloud.length &&
list.push({
list: [...cloud],
title: '云平台接入',
});
channel.length &&
list.push({
list: [...channel],
title: '通道类设备接入',
});
edge.length &&
list.push({
list: [...edge],
title: '官方接入',
});
return list;
};
const queryProviders = async () => {
const resp = await getProviders();
if (resp.status === 200) {
const media: any[] = [];
const network: any[] = [];
const cloud: any[] = [];
const channel: any[] = [];
const edge: any[] = [];
resp.result.map((item) => {
if (item.id === 'fixed-media' || item.id === 'gb28181-2016') {
item.type = 'media';
media.push(item);
} else if (item.id === 'OneNet' || item.id === 'Ctwing') {
item.type = 'cloud';
cloud.push(item);
} else if (item.id === 'modbus-tcp' || item.id === 'opc-ua') {
item.type = 'channel';
channel.push(item);
} else if (
item.id === 'official-edge-gateway' ||
item.id === 'edge-child-device'
) {
item.type = 'edge';
edge.push(item);
} else {
item.type = 'network';
network.push(item);
}
});
const list = [];
if (network.length) {
list.push({
// type: 'network',
list: [...network],
title: '自定义设备接入',
});
}
if (media.length) {
list.push({
// type: 'media',
list: [...media],
title: '视频类设备接入',
});
}
if (cloud.length) {
list.push({
// type: 'cloud',
list: [...cloud],
title: '云平台接入',
});
}
if (channel.length) {
list.push({
// type: 'channel',
list: [...channel],
title: '通道类设备接入',
});
}
if (edge.length) {
list.push({
// type: 'edge',
list: [...edge],
title: '官方接入',
});
}
dataSource.value = list;
dataSource.value = getTypeList(resp.result);
}
};
const getProvidersData = async () => {
if (id) {
if (id && modeType !== 'add') {
getProviders().then((response) => {
if (response.status === 200) {
dataSource.value = response.result.filter(
const list = getTypeList(response.result);
dataSource.value = list.filter(
(item) =>
item.channel === 'network' ||
item.channel === 'child-device',
@ -144,15 +152,21 @@ const getProvidersData = async () => {
const dt = response.result.find(
(item) => item?.id === resp.result.provider,
);
response.result.forEach((item) => {
if (item.id === resp.result.provider) {
resp.result.type = item.type;
showType.value = item.type;
}
});
provider.value = dt;
data.value = resp.result;
type.value = false;
}
});
loading.value = false;
} else {
loading.value = false;
}
loading.value = false;
});
} else {
type.value = true;

View File

@ -39,7 +39,10 @@
/>
</a-form-item>
<a-form-item>
<a-button type="primary" html-type="submit"
<a-button
v-if="modeType !== 'view'"
type="primary"
html-type="submit"
>保存</a-button
>
</a-form-item>
@ -93,18 +96,23 @@ interface FormState {
description: string;
}
const route = useRoute();
const id = route.query.id;
const modeType = route.params.type as string;
const id = route.params.id as string;
const props = defineProps({
provider: {
type: Object,
default: () => {},
},
data: {
type: Object,
default: () => {},
},
});
const type = ref(props.provider.type);
const formState = reactive<FormState>({
const formState = ref<FormState>({
name: '',
description: '',
});
@ -117,7 +125,10 @@ const onFinish = async (values: any) => {
transport: ProtocolMapping.get(providerId),
channel: providerId === 'modbus-tcp' ? 'modbus' : 'opc-ua',
};
const resp = !!id ? await update({ ...params, id }) : await save(params);
const resp =
!!id && modeType !== 'add'
? await update({ ...params, id })
: await save(params);
if (resp.status === 200) {
message.success('操作成功!');
// if (params.get('save')) {
@ -132,6 +143,15 @@ const onFinish = async (values: any) => {
// }
}
};
onMounted(() => {
if (modeType !== 'add') {
formState.value = {
name: props.data.name,
description: props.data?.description || '',
};
}
});
</script>
<style lang="less" scoped>

View File

@ -199,7 +199,7 @@
<title-component data="基本信息" />
<div>
<a-form
:model="form"
:model="formData"
name="basic"
autocomplete="off"
layout="vertical"
@ -219,14 +219,14 @@
>
<a-input
placeholder="请输入名称"
v-model:value="form.name"
v-model:value="formData.name"
/>
</a-form-item>
<a-form-item label="说明" name="description">
<a-textarea
placeholder="请输入说明"
:rows="4"
v-model:value="form.description"
v-model:value="formData.description"
show-count
:maxlength="200"
/>
@ -277,16 +277,20 @@
<a-button
v-if="[0, 1].includes(current)"
type="primary"
style="margin-right: 8px"
@click="next"
>
下一步
</a-button>
<a-button v-if="current === 2" type="primary" @click="saveData">
<a-button
v-if="current === 2 && modeType !== 'view'"
type="primary"
style="margin-right: 8px"
@click="saveData"
>
保存
</a-button>
<a-button v-if="current > 0" style="margin-left: 8px" @click="prev">
上一步
</a-button>
<a-button v-if="current > 0" @click="prev"> 上一步 </a-button>
</div>
</div>
</template>
@ -379,7 +383,8 @@ interface Form {
description: string;
}
const route = useRoute();
const id = route.query.id;
const modeType = route.params.type as string;
const id = route.params.id as string;
const props = defineProps({
provider: {
@ -396,13 +401,13 @@ const channel = ref(props.provider.channel);
const formRef1 = ref<FormInstance>();
const formRef2 = ref<FormInstance>();
const formState = reactive<FormState>({
const formState = ref<FormState>({
apiAddress: 'https://ag-api.ctwing.cn/',
appKey: '',
appSecret: '',
description: '',
});
const form = reactive<Form>({
const formData = ref<Form>({
name: '',
description: '',
});
@ -415,9 +420,7 @@ const allProcotolList = ref([]);
const procotolCurrent = ref('');
const procotolChange = (id: string) => {
if (!props.data?.id) {
procotolCurrent.value = id;
}
procotolCurrent.value = id;
};
const procotolSearch = (value: string) => {
@ -439,7 +442,7 @@ const saveData = async () => {
const params = {
...data,
configuration: {
...formState,
...formState.value,
protocol: procotolCurrent.value,
},
protocol: procotolCurrent.value,
@ -447,7 +450,7 @@ const saveData = async () => {
transport: 'HTTP_SERVER',
};
const resp =
props.data && props.data.id
!!id && modeType !== 'add'
? await update({
...props.data,
...params,
@ -514,7 +517,16 @@ const next = async () => {
const prev = () => {
current.value = current.value - 1;
};
onMounted(() => {
if (modeType !== 'add') {
formState.value = props.data.configuration;
procotolCurrent.value = props.data.protocol;
formData.value = {
name: props.data.name,
description: props.data.description,
};
}
});
watch(
current,
(v) => {

View File

@ -292,7 +292,7 @@
<title-component data="基本信息" />
<div>
<a-form
:model="form"
:model="formData"
name="basic"
autocomplete="off"
layout="vertical"
@ -312,26 +312,18 @@
>
<a-input
placeholder="请输入名称"
v-model:value="form.name"
v-model:value="formData.name"
/>
</a-form-item>
<a-form-item label="说明" name="description">
<a-textarea
placeholder="请输入说明"
:rows="4"
v-model:value="form.description"
v-model:value="formData.description"
show-count
:maxlength="200"
/>
</a-form-item>
<!-- <a-form-item>
<a-button
v-if="current !== 1"
type="primary"
html-type="submit"
>保存</a-button
>
</a-form-item> -->
</a-form>
</div>
</a-col>
@ -378,16 +370,20 @@
<a-button
v-if="[0, 1].includes(current)"
type="primary"
style="margin-right: 8px"
@click="next"
>
下一步
</a-button>
<a-button v-if="current === 2" type="primary" @click="saveData">
<a-button
style="margin-right: 8px"
v-if="current === 2 && modeType !== 'view'"
type="primary"
@click="saveData"
>
保存
</a-button>
<a-button v-if="current > 0" style="margin-left: 8px" @click="prev">
上一步
</a-button>
<a-button v-if="current > 0" @click="prev"> 上一步 </a-button>
</div>
</div>
</template>
@ -483,7 +479,8 @@ interface Form {
description: string;
}
const route = useRoute();
const id = route.query.id;
const modeType = route.params.type as string;
const id = route.params.id as string;
const props = defineProps({
provider: {
@ -500,14 +497,14 @@ const channel = ref(props.provider.channel);
const formRef1 = ref<FormInstance>();
const formRef2 = ref<FormInstance>();
const formState = reactive<FormState>({
const formState = ref<FormState>({
apiAddress: 'https://api.heclouds.com/',
apiKey: '',
validateToken: '',
aesKey: '',
description: '',
});
const form = reactive<Form>({
const formData = ref<Form>({
name: '',
description: '',
});
@ -520,9 +517,7 @@ const allProcotolList = ref([]);
const procotolCurrent = ref('');
const procotolChange = (id: string) => {
if (!props.data?.id) {
procotolCurrent.value = id;
}
procotolCurrent.value = id;
};
const procotolSearch = (value: string) => {
@ -544,7 +539,7 @@ const saveData = async () => {
const params = {
...data,
configuration: {
...formState,
...formState.value,
protocol: procotolCurrent.value,
},
protocol: procotolCurrent.value,
@ -552,7 +547,7 @@ const saveData = async () => {
transport: 'HTTP_SERVER',
};
const resp =
props.data && props.data.id
!!id && modeType !== 'add'
? await update({
...props.data,
...params,
@ -619,6 +614,17 @@ const prev = () => {
current.value = current.value - 1;
};
onMounted(() => {
if (modeType !== 'add') {
formState.value = props.data.configuration;
procotolCurrent.value = props.data.protocol;
formData.value = {
name: props.data.name,
description: props.data.description,
};
}
});
watch(
current,
(v) => {

View File

@ -17,9 +17,6 @@
import Ctwing from './Ctwing.vue';
import OneNet from './OneNet.vue';
const route = useRoute();
const id = route.query.id;
const props = defineProps({
provider: {
type: Object,

View File

@ -140,7 +140,7 @@
</a-form-item>
<a-form-item>
<a-button
v-if="current !== 1"
v-if="current !== 1 && modeType !== 'view'"
type="primary"
html-type="submit"
>保存</a-button
@ -171,15 +171,22 @@
v-if="channel !== 'edge-child-device'"
:class="current !== 1 ? 'steps-action' : 'steps-action-save'"
>
<a-button v-if="[0].includes(current)" @click="next">
<a-button
v-if="[0].includes(current)"
style="margin-right: 8px"
@click="next"
>
下一步
</a-button>
<a-button v-if="current === 1" type="primary" @click="saveData">
<a-button
v-if="current === 1 && modeType !== 'view'"
type="primary"
style="margin-right: 8px"
@click="saveData"
>
保存
</a-button>
<a-button v-if="current > 0" style="margin-left: 8px" @click="prev">
上一步
</a-button>
<a-button v-if="current > 0" @click="prev"> 上一步 </a-button>
</div>
</div>
</template>
@ -290,19 +297,25 @@ interface FormState {
description: string;
}
const route = useRoute();
const id = route.query.id;
const modeType = route.params.type as string;
const id = route.params.id as string;
const props = defineProps({
provider: {
type: Object,
default: () => {},
},
data: {
type: Object,
default: () => {},
},
});
const type = ref(props.provider.type);
const type = props.provider.type;
const channel = ref(props.provider.channel);
const formState = reactive<FormState>({
const formState = ref<FormState>({
name: '',
description: '',
});
@ -324,9 +337,10 @@ const onFinish = async (values: any) => {
transport: ProtocolMapping.get(providerId),
};
if (networkCurrent.value) params.channelId = networkCurrent.value;
console.log(1112, networkCurrent.value, params);
const resp = !!id ? await update({ ...params, id }) : await save(params);
const resp =
!!id && modeType !== 'add'
? await update({ ...params, id })
: await save(params);
if (resp.status === 200) {
message.success('操作成功!');
// if (params.get('save')) {
@ -407,6 +421,13 @@ onMounted(() => {
if (props.provider.id === 'official-edge-gateway') {
queryNetworkList(props.provider.id, '');
}
if (modeType !== 'add') {
formState.value = {
name: props.data.name,
description: props.data?.description || '',
};
networkCurrent.value = props.data.channelId;
}
}),
watch(
current,

View File

@ -436,17 +436,18 @@
<a-col :span="12">
<title-component data="基本信息" />
<div>
<a-form :model="form" layout="vertical">
<a-form :model="formData" layout="vertical">
<a-form-item
label="名称"
v-bind="validateInfos.name"
>
<a-input
v-model:value="form.name"
v-model:value="formData.name"
allowClear
placeholder="请输入名称"
/>
</a-form-item>
<a-form-item
label="说明"
v-bind="validateInfos.description"
@ -454,7 +455,7 @@
<a-textarea
placeholder="请输入说明"
:rows="4"
v-model:value="form.description"
v-model:value="formData.description"
show-count
:maxlength="200"
/>
@ -494,15 +495,22 @@
</div>
</div>
<div class="steps-action">
<a-button v-if="[0].includes(current)" @click="next">
<a-button
v-if="[0].includes(current)"
style="margin-right: 8px"
@click="next"
>
下一步
</a-button>
<a-button v-if="current === 1" type="primary" @click="saveData">
<a-button
v-if="current === 1 && modeType !== 'view'"
type="primary"
style="margin-right: 8px"
@click="saveData"
>
保存
</a-button>
<a-button v-if="current > 0" style="margin-left: 8px" @click="prev">
上一步
</a-button>
<a-button v-if="current > 0" @click="prev"> 上一步 </a-button>
</div>
</div>
</template>
@ -544,10 +552,15 @@ const props = defineProps({
type: Object,
default: () => {},
},
data: {
type: Object,
default: () => {},
},
});
const route = useRoute();
const id = route.query.id;
const modeType = route.params.type as string;
const id = route.params.id as string;
const activeKey: any = ref([]);
const clientHeight = document.body.clientHeight;
@ -559,11 +572,11 @@ const useForm = Form.useForm;
const current = ref(0);
const stepCurrent = ref(0);
const steps = ref(['信令配置', '完成']);
const form = reactive({
const formData = ref({
name: '',
description: '',
});
const formState = reactive<FormState>({
let formState = ref<FormState>({
domain: '',
sipId: '',
shareCluster: true,
@ -574,6 +587,7 @@ const formState = reactive<FormState>({
publicHost: '',
},
});
let params = {
configuration: {},
};
@ -628,12 +642,13 @@ const handleChangeForm2Sip = (index: number) => {
};
const { resetFields, validate, validateInfos } = useForm(
form,
formData,
reactive({
name: [
{ required: true, message: '请输入名称', trigger: 'blur' },
{ max: 64, message: '最多可输入64个字符' },
],
description: [{ max: 200, message: '最多可输入200个字符' }],
}),
);
@ -646,9 +661,11 @@ const saveData = () => {
transport: 'SIP',
channel: 'gb28181',
};
const resp = !!id
? await update({ ...params, id })
: await save(params);
const resp =
!!id && modeType !== 'add'
? await update({ ...params, id })
: await save(params);
if (resp.status === 200) {
message.success('操作成功!');
// if (params.get('save')) {
@ -723,6 +740,14 @@ onMounted(() => {
clustersList.value = list;
}
});
if (modeType !== 'add') {
formState.value = props.data.configuration;
formData.value = {
name: props.data.name,
description: props.data?.description || '',
};
}
});
watch(

View File

@ -39,7 +39,10 @@
/>
</a-form-item>
<a-form-item>
<a-button type="primary" html-type="submit"
<a-button
v-if="modeType !== 'view'"
type="primary"
html-type="submit"
>保存</a-button
>
</a-form-item>
@ -72,7 +75,7 @@
</a-row>
</div>
<div v-else-if="channel === 'gb28181'">
<GB28181 :provider="props.provider"></GB28181>
<GB28181 :provider="props.provider" :data="props.data"></GB28181>
</div>
</div>
</template>
@ -88,18 +91,23 @@ interface FormState {
description: string;
}
const route = useRoute();
const id = route.query.id;
const modeType = route.params.type as string;
const id = route.params.id as string;
const props = defineProps({
provider: {
type: Object,
default: () => {},
},
data: {
type: Object,
default: () => {},
},
});
const channel = ref(props.provider.channel);
const formState = reactive<FormState>({
const formState = ref<FormState>({
name: '',
description: '',
});
@ -110,7 +118,10 @@ const onFinish = async (values: any) => {
transport: 'URL',
channel: 'fixed-media',
};
const resp = !!id ? await update({ ...params, id }) : await save(params);
const resp =
!!id && modeType !== 'add'
? await update({ ...params, id })
: await save(params);
if (resp.status === 200) {
message.success('操作成功!');
// if (params.get('save')) {
@ -125,6 +136,15 @@ const onFinish = async (values: any) => {
// }
}
};
onMounted(() => {
if (modeType !== 'add') {
formState.value = {
name: props.data.name,
description: props.data?.description || '',
};
}
});
</script>
<style lang="less" scoped>

View File

@ -138,7 +138,7 @@
<div>
<a-form
ref="formRef"
:model="form"
:model="formData"
layout="vertical"
>
<a-form-item
@ -146,7 +146,7 @@
v-bind="validateInfos.name"
>
<a-input
v-model:value="form.name"
v-model:value="formData.name"
allowClear
placeholder="请输入名称"
/>
@ -158,7 +158,7 @@
<a-textarea
placeholder="请输入说明"
:rows="4"
v-model:value="form.description"
v-model:value="formData.description"
show-count
:maxlength="200"
/>
@ -295,13 +295,26 @@
</div>
</div>
<div class="steps-action">
<a-button v-if="[0, 1].includes(current)" @click="next">
<a-button
v-if="[0, 1].includes(current)"
type="primary"
style="margin-right: 8px"
@click="next"
>
下一步
</a-button>
<a-button v-if="current === 2" type="primary" @click="saveData">
<a-button
v-if="current === 2 && modeType !== 'view'"
type="primary"
style="margin-right: 8px"
@click="saveData"
>
保存
</a-button>
<a-button v-if="current > 0" style="margin-left: 8px" @click="prev">
<a-button
v-if="type === 'child-device' ? current > 1 : current > 0"
@click="prev"
>
上一步
</a-button>
</div>
@ -524,7 +537,204 @@ const result2 = {
'### 认证说明\r\n\r\nCONNECT报文:\r\n```text\r\nclientId: 设备ID\r\nusername: secureId+"|"+timestamp\r\npassword: md5(secureId+"|"+timestamp+"|"+secureKey)\r\n ```\r\n\r\n说明: secureId以及secureKey在创建设备产品或设备实例时进行配置. \r\ntimestamp为当前时间戳(毫秒),与服务器时间不能相差5分钟.\r\nmd5为32位,不区分大小写.',
metadata: '',
};
//
const networkData = {
COAP_SERVER: [
{
id: '1620352949679960064',
name: '前端测试1',
description: '前端测试1',
addresses: [
{
address: 'coap://111.0.0:88',
health: 1,
ok: true,
bad: false,
disabled: false,
},
],
},
{
id: '1613071630619607040',
name: '1',
addresses: [
{
address: 'coap://120.77.179.54:9000',
health: 1,
ok: true,
bad: false,
disabled: false,
},
],
},
],
UDP: [
{
id: '1590553821093437440',
name: '194',
addresses: [
{
address: 'udp://139.217.130.194:1883',
health: 1,
ok: true,
bad: false,
disabled: false,
},
],
},
{
id: '1585831257204301824',
name: '测试隐藏集群',
description: '111',
addresses: [
{
address: 'udp://127.0.0.1:8080',
health: 1,
ok: true,
bad: false,
disabled: false,
},
],
},
{
id: '1584825665263149056',
name: '1',
addresses: [
{
address: 'udp://120.77.179.54:9000',
health: 1,
ok: true,
bad: false,
disabled: false,
},
],
},
{
id: '1551761481741672448',
name: '0726UDP',
description: '测试',
addresses: [
{
address: 'udp://120.77.179.54:8088',
health: 1,
ok: true,
bad: false,
disabled: false,
},
],
},
],
TCP_SERVER: [
{
id: '1603206069979918336',
name: '测试A',
addresses: [
{
address: 'tcp://120.77.179.54:8106',
health: -1,
ok: false,
bad: false,
disabled: true,
},
],
},
{
id: '1603206069979918330',
name: '测试AA',
addresses: [
{
address: 'tcp://120.77.179.54:8106',
health: -1,
ok: false,
bad: false,
disabled: true,
},
],
},
],
MQTT_SERVER: [
{
id: '1585192878304051200',
name: 'MQTT网络组件',
addresses: [
{
address: 'mqtt://120.77.179.54:8101',
health: 1,
ok: true,
bad: false,
disabled: false,
},
],
},
{
id: '1583268266806009856',
name: '我的第一个MQTT服务组件',
description: '',
addresses: [
{
address: 'mqtt://120.77.179.54:8100',
health: 1,
ok: true,
bad: false,
disabled: false,
},
],
},
{
id: '1570335308902912000',
name: '0915MQTT网络组件_勿动',
description: '测试,勿动!',
addresses: [
{
address: 'mqtt://120.77.179.54:8083',
health: 1,
ok: true,
bad: false,
disabled: false,
},
],
},
{
id: '1567062350140858368',
name: '网络组件20220906160907',
addresses: [
{
address: 'mqtt://120.77.179.54:8083',
health: 1,
ok: true,
bad: false,
disabled: false,
},
],
},
{
id: '1556563257890742272',
name: 'MQTT网络组件',
addresses: [
{
address: 'mqtt://0.0.0.0:8104',
health: 1,
ok: true,
bad: false,
disabled: false,
},
],
},
{
id: '1534774770408108032',
name: 'MQTT',
addresses: [
{
address: 'mqtt://120.77.179.54:8088',
health: 1,
ok: true,
bad: false,
disabled: false,
},
],
},
],
};
function generateUUID() {
var d = new Date().getTime();
if (
@ -555,6 +765,10 @@ const props = defineProps({
});
const clientHeight = document.body.clientHeight;
const type = props.provider.channel;
const route = useRoute();
const modeType = route.params.type as string;
const id = route.params.id as string;
const formRef = ref<FormInstance>();
const useForm = Form.useForm;
@ -569,29 +783,40 @@ const networkCurrent = ref('');
const procotolCurrent = ref('');
let config = ref({});
let columnsMQTT = ref(<TableColumnType>[]);
const form = reactive({
const formData = ref({
name: '',
description: '',
});
const { resetFields, validate, validateInfos } = useForm(
form,
formData,
reactive({
name: [
{ required: true, message: '请输入名称', trigger: 'blur' },
{ max: 64, message: '最多可输入64个字符' },
],
description: [{ max: 200, message: '最多可输入200个字符' }],
}),
);
const queryNetworkList = async (id: string, include: string, data = {}) => {
if (NetworkTypeMapping.get(id) === 'MQTT_SERVER') {
//使
networkList.value = networkData[NetworkTypeMapping.get(id)];
// return;
}
const resp = await getNetworkList(
NetworkTypeMapping.get(id),
include,
data,
);
if (resp.status === 200) {
networkList.value = resp.result;
//使
// networkList.value = resp.result;
networkList.value =
resp.result.length === 0
? networkData[NetworkTypeMapping.get(id)]
: resp.result;
}
};
@ -686,7 +911,7 @@ const saveData = () => {
channel: 'network', //
channelId: networkCurrent.value,
};
if (props.data && props.data.id) {
if (!!id && modeType !== 'add') {
resp = await update(params);
} else {
params = {
@ -702,12 +927,13 @@ const saveData = () => {
if (resp.status === 200) {
message.success('操作成功!');
//
if (window.onTabSaveSuccess) {
window.onTabSaveSuccess(resp);
setTimeout(() => window.close(), 300);
} else {
// this.$store.dispatch('jumpPathByKey', { key: MenuKeys['Link/AccessConfig'] })
}
// if (window.onTabSaveSuccess) {
// window.onTabSaveSuccess(resp);
// setTimeout(() => window.close(), 300);
// } else {
// // this.$store.dispatch('jumpPathByKey', { key: MenuKeys['Link/AccessConfig'] })
// }
history.back();
}
})
.catch((err) => {});
@ -781,7 +1007,7 @@ const next = async () => {
];
// const resp =
// props.provider.channel !== 'child-device'
// type !== 'child-device'
// ? await getConfigView(
// procotolCurrent.value,
// ProtocolMapping.get(props.provider.id),
@ -864,7 +1090,7 @@ onMounted(() => {
}
} else {
if (props.provider?.id) {
if (props.provider.channel !== 'child-device') {
if (type !== 'child-device') {
queryNetworkList(props.provider.id, '');
steps.value = ['网络组件', '消息协议', '完成'];
current.value = 0;
@ -877,14 +1103,20 @@ onMounted(() => {
}
});
onMounted(() => {
if (modeType !== 'add') {
procotolCurrent.value = props.data.protocol;
formData.value = {
name: props.data.name,
description: props.data.description,
};
}
});
watch(
current,
(v) => {
if (props.provider.channel !== 'child-device') {
stepCurrent.value = v;
} else {
stepCurrent.value = v - 1;
}
stepCurrent.value = type === 'child-device' ? v - 1 : v;
},
{
deep: true,

View File

@ -44,8 +44,13 @@
</template>
<template #content>
<div class="card-item-content">
<h3 class="card-item-content-title">
<a href="">{{ slotProps.name }}</a>
<h3
@click="handlEye(slotProps.id)"
class="card-item-content-title"
>
<a class="card-item-content-title-a">{{
slotProps.name
}}</a>
</h3>
<a-row class="card-item-content-box">
<a-col
@ -110,19 +115,23 @@
<a-tooltip>
<template #title>
{{
providersList.find(
(item) =>
item.id ===
slotProps.provider,
)?.description
}}</template
>
slotProps.description
? slotProps.description
: providersList.find(
(item) =>
item.id ===
slotProps.provider,
)?.description
}}
</template>
{{
providersList.find(
(item) =>
item.id ===
slotProps.provider,
)?.description
slotProps.description
? slotProps.description
: providersList.find(
(item) =>
item.id ===
slotProps.provider,
)?.description
}}
</a-tooltip>
</div>
@ -184,7 +193,13 @@
<script lang="ts" setup name="AccessConfigPage">
import type { ActionsType } from '@/components/Table/index.vue';
import { getImage } from '@/utils/comm';
import { list, getProviders } from '@/api/link/accessConfig';
import {
list,
getProviders,
remove,
undeploy,
deploy,
} from '@/api/link/accessConfig';
import { message } from 'ant-design-vue';
const tableRef = ref<Record<string, any>>({});
@ -261,9 +276,8 @@ const columns = [
];
const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
if (!data) {
return [];
}
if (!data) return [];
const state = data.state.value;
return [
{
key: 'edit',
@ -276,15 +290,47 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
handlEdit(data.id);
},
},
{
key: 'action',
text: state === 'enabled' ? '禁用' : '启用',
tooltip: {
title: state === 'enabled' ? '禁用' : '启用',
},
icon: state === 'enabled' ? 'StopOutlined' : 'CheckCircleOutlined',
popConfirm: {
title: `确认${state === 'enabled' ? '禁用' : '启用'}?`,
onConfirm: async () => {
let res =
state === 'enabled'
? await undeploy(data.id)
: await deploy(data.id);
if (res.success) {
message.success('操作成功');
tableRef.value?.reload();
} else {
message.error('操作失败!');
}
},
},
},
{
key: 'delete',
text: '删除',
disabled: state === 'enabled',
tooltip: {
title: state === 'enabled' ? '已启用的设备不能删除' : '删除',
},
popConfirm: {
title: '确认删除?',
okText: ' 确定',
cancelText: '取消',
onConfirm: async () => {
handlDelete(data.id);
const res = await remove(data.id);
if (res.success) {
message.success('操作成功');
tableRef.value.reload();
} else {
message.error('操作失败!');
}
},
},
icon: 'DeleteOutlined',
@ -299,21 +345,13 @@ const getProvidersList = async () => {
getProvidersList();
const handlAdd = () => {
router.push('/link/accessConfig/detail/add');
// router.push('/link/certificate/detail/add/new');
router.push('/link/accessConfig/detail/add/new');
};
const handlEdit = (id: string) => {
router.push(`/link/certificate/detail/edit/${id}`);
router.push(`/link/accessConfig/detail/edit/${id}`);
};
const handlDelete = async (id: string) => {
const res = await remove(id);
if (res.success) {
message.success('操作成功');
tableRef.value.reload();
}
const handlEye = (id: string) => {
router.push(`/link/accessConfig/detail/view/${id}`);
};
/**
@ -356,6 +394,14 @@ const handleSearch = (e: any) => {
.card-item-content {
min-height: 100px;
.card-item-content-title-a {
// color: #000 !important;
font-weight: 700;
font-size: 18px;
overflow: hidden; //
text-overflow: ellipsis; //
white-space: nowrap; //
}
.card-item-content-box {
min-height: 50px;
}

View File

@ -60,7 +60,7 @@
<a-form-item>
<a-button
v-if="type !== 'view'"
v-if="modeType !== 'view'"
class="form-submit"
html-type="submit"
type="primary"
@ -103,7 +103,7 @@ import { FormDataType, TypeObjType } from '../type';
const router = useRouter();
const route = useRoute();
const type = route.params.type as string;
const modeType = route.params.type as string;
const id = route.params.id as string;
const useForm = Form.useForm;
@ -145,7 +145,7 @@ const onSubmit = () => {
const params = toRaw(formData.value);
loading.value = true;
const response =
type === 'edit' ? await update(params) : await save(params);
modeType === 'edit' ? await update(params) : await save(params);
if (response.status === 200) {
message.success('操作成功');
router.push('/link/certificate');
@ -168,7 +168,7 @@ const handleChange = (info: UploadChangeParam) => {
};
const detail = async (id: string) => {
if (type !== 'add') {
if (modeType !== 'add') {
loading.value = true;
const res = await queryDetail(id);
if (res.success) {

View File

@ -0,0 +1,3 @@
<template>
123
</template>

View File

@ -148,7 +148,7 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
}
const add = () => {
message.warn('123')
// router.push(`/northbound/DuerOS/detail/:id`)
}
</script>

View File

@ -64,7 +64,7 @@
import { Form } from 'ant-design-vue';
import { PropType } from 'vue';
import ConfigApi from '@/api/notice/config';
import {
import type {
TemplateFormData,
IVariableDefinitions,
} from '@/views/notice/Template/types';

View File

@ -36,7 +36,7 @@
<script setup lang="ts">
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
import { PropType } from 'vue';
import { IHeaders } from '../../types';
import type { IHeaders } from '../../types';
type Emits = {
(e: 'update:headers', data: IHeaders[]): void;

View File

@ -279,7 +279,7 @@
import { getImage } from '@/utils/comm';
import { Form } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { ConfigFormData } from '../types';
import type { ConfigFormData } from '../types';
import {
NOTICE_METHOD,
CONFIG_FIELD_MAP,

View File

@ -91,7 +91,10 @@
v-for="(o, i) in item.children"
:key="i"
>
<a-button type="link" @click="o.onClick">
<a-button
type="link"
@click="o.onClick"
>
<AIcon :type="o.icon" />
<span>{{ o.text }}</span>
</a-button>
@ -167,9 +170,8 @@
<script setup lang="ts">
import ConfigApi from '@/api/notice/config';
import type { ActionsType } from '@/components/Table/index.vue';
import { getImage, LocalStore } from '@/utils/comm';
import { message } from 'ant-design-vue';
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
import { NOTICE_METHOD, MSG_TYPE } from '@/views/notice/const';
import SyncUser from './SyncUser/index.vue';
@ -266,7 +268,7 @@ const getMethodTxt = (type: string) => {
* 新增
*/
const handleAdd = () => {
router.push(`/notice/Config/detail/:id`);
router.push(`/iot/notice/Config/detail/:id`);
};
/**
@ -306,13 +308,6 @@ const handleExport = () => {
downloadObject(configRef.value.dataSource, `通知配置`);
};
/**
* 查看
*/
const handleView = (id: string) => {
message.warn(id + '暂未开发');
};
const syncVis = ref(false);
const debugVis = ref(false);
const logVis = ref(false);
@ -333,7 +328,7 @@ const getActions = (
onClick: () => {
// visible.value = true;
// current.value = data;
router.push(`/notice/Config/detail/${data.id}`);
router.push(`/iot/notice/Config/detail/${data.id}`);
},
},
{
@ -360,35 +355,6 @@ const getActions = (
currentConfig.value = data;
},
},
// {
// key: 'others',
// text: '',
// children: [
// {
// key: 'debug',
// text: '',
// tooltip: {
// title: '',
// },
// icon: 'ArrowDownOutlined',
// onClick: () => {
// downloadObject(data, ``);
// },
// },
// {
// key: 'sync',
// text: '',
// tooltip: {
// title: '',
// },
// icon: 'TeamOutlined',
// onClick: () => {
// syncVis.value = true;
// currentConfig.value = data;
// },
// },
// ],
// },
{
key: 'delete',
text: '删除',

View File

@ -63,7 +63,7 @@
import { Form } from 'ant-design-vue';
import { PropType } from 'vue';
import TemplateApi from '@/api/notice/template';
import {
import type {
TemplateFormData,
IVariableDefinitions,
BindConfig,

View File

@ -47,7 +47,7 @@ import {
UploadOutlined,
} from '@ant-design/icons-vue';
import { PropType } from 'vue';
import { IAttachments } from '../../types';
import type { IAttachments } from '../../types';
import { FILE_UPLOAD } from '@/api/comm';
import { LocalStore } from '@/utils/comm';
import { TOKEN_KEY } from '@/utils/variable';

View File

@ -477,7 +477,7 @@
import { getImage } from '@/utils/comm';
import { Form, UploadChangeParam } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { IVariableDefinitions, TemplateFormData } from '../types';
import type { IVariableDefinitions, TemplateFormData } from '../types';
import {
NOTICE_METHOD,
TEMPLATE_FIELD_MAP,

View File

@ -256,7 +256,7 @@ const getMethodTxt = (type: string) => {
* 新增
*/
const handleAdd = () => {
router.push(`/notice/Template/detail/:id`);
router.push(`/iot/notice/Template/detail/:id`);
};
/**
@ -323,7 +323,7 @@ const getActions = (
onClick: () => {
// visible.value = true;
// current.value = data;
router.push(`/notice/Template/detail/${data.id}`);
router.push(`/iot/notice/Template/detail/${data.id}`);
},
},
{

View File

@ -170,22 +170,18 @@
placeholder="请选择关联菜单"
multiple
show-search
tree-default-expand-all
:tree-data="form.treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id',
}"
>
<template #title="{ value: val, title }">
<b
v-if="val === 'parent 1-1'"
style="color: #08c"
>{{ val }}</b
>
<template v-else>{{ title }}</template>
</template>
</a-tree-select>
</a-form-item>
</a-form-item>
<a-form-item label="权限">
<PermissChoose v-model:value="form.data.permissions" />
<PermissChoose :first-width="3" max-height="350px" v-model:value="form.data.permissions" />
</a-form-item>
</a-form>
@ -245,17 +241,16 @@ const form = reactive({
treeData: [], //
assetsType: [] as assetType[], //
premissonList: [], //
init: () => {
//
routeParams.id &&
getMenuInfo_api(routeParams.id).then((resp) => {
console.log('菜单详情', resp);
form.data = resp.result as formType
});
//
getMenuTree_api({ paging: false }).then((resp) => {
console.log('关联菜单', resp);
getMenuTree_api({ paging: false }).then((resp: any) => {
form.treeData = resp.result;
});
//
getAssetsType_api().then((resp: any) => {

View File

@ -1,18 +1,134 @@
<template>
<div class="button-mange-container">
<JTable
ref="tableRef"
:columns="table.columns"
model="TABLE"
:dataSource="table.data"
>
<template #headerTitle>
<a-button
type="primary"
style="margin-right: 10px"
@click="() => dialog.openDialog()"
><plus-outlined />新增</a-button
>
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip>
<template #title>编辑</template>
<a-button
style="padding: 0"
type="link"
@click="() => dialog.openDialog(slotProps)"
>
<edit-outlined />
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>查看</template>
<a-button
style="padding: 0"
type="link"
@click="() => dialog.openDialog(slotProps)"
>
<edit-outlined />
</a-button>
</a-tooltip>
<a-popconfirm
title="确认删除"
ok-text="确定"
cancel-text="取消"
:disabled="slotProps.status"
>
<a-tooltip>
<template #title>删除</template>
<a-button style="padding: 0" type="link">
<delete-outlined />
</a-button>
</a-tooltip>
</a-popconfirm>
</a-space>
</template>
</JTable>
<div class="dialog">
<ButtonAddDialog ref="dialogRef" @confirm="dialog.confirm" />
</div>
</div>
</template>
<script setup lang="ts">
import {
EditOutlined,
DeleteOutlined,
PlusOutlined,
} from '@ant-design/icons-vue';
import ButtonAddDialog from '../components/ButtonAddDialog.vue';
import { getMenuInfo_api } from '@/api/system/menu';
//
const route = useRoute();
const routeParams = {
id: route.params.id === ':id' ? '' : route.params.id,
id: route.params.id === ':id' ? '' : (route.params.id as string),
...route.query,
};
//
const dialogRef = ref<any>(null);
const dialog = {
//
openDialog: (row?: object) => {
dialogRef.value && dialogRef.value.openDialog(row);
},
confirm: () => {},
};
//
const table = reactive({
columns: [
{
title: '编码',
dataIndex: 'id',
key: 'id',
width: 220,
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
width: 300,
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
scopedSlots: true,
width: 240,
},
],
data: [] as tableDataItem[],
getList: () => {
routeParams.id &&
getMenuInfo_api(routeParams.id).then((resp: any) => {
table.data = resp.result.buttons as tableDataItem[];
});
},
});
table.getList();
type tableDataItem = {
id: string;
name: string;
description?: string;
permissions: object[];
};
</script>
<style scoped>
</style>
<style scoped></style>

View File

@ -0,0 +1,97 @@
<template>
<a-modal
v-model:visible="dialog.visible"
title="新增"
width="660px"
@ok="dialog.handleOk"
>
<a-form :model="form.data" class="basic-form">
<a-form-item
label="编码"
name="id"
:rules="[
{ required: true, message: '请输入编码' },
{ max: 64, message: '最多可输入64个字符' },
]"
>
<a-input v-model:value="form.data.id" />
</a-form-item>
<a-form-item
label="名称"
name="name"
:rules="[
{ required: true, message: '请输入名称' },
{ max: 64, message: '最多可输入64个字符' },
]"
>
<a-input v-model:value="form.data.name" />
</a-form-item>
<a-form-item label="权限">
<PermissChoose
:first-width="8"
max-height="350px"
v-model:value="form.data.permissions"
/>
</a-form-item>
<a-form-item label="说明" name="describe">
<a-textarea
v-model:value="form.data.describe"
:rows="4"
placeholder="请输入说明"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script setup lang="ts">
import PermissChoose from '../components/PermissChoose.vue';
const emits = defineEmits(['confirm']);
const dialog = reactive({
visible: false,
handleOk: () => {
dialog.changeVisible();
},
changeVisible: (formValue?: formType, show?: boolean) => {
dialog.visible = show === undefined ? !dialog.visible : show;
form.data = formValue || { ...initForm };
console.log(1111111111, form.data);
},
});
const initForm = {
name: '',
id: '',
permissions: [],
describe: '',
} as formType;
const form = reactive({
data: { ...initForm },
});
//
defineExpose({
openDialog: dialog.changeVisible,
});
type formType = {
name: string;
id: string;
permissions: any[];
describe: string;
};
</script>
<style lang="less" scoped>
.basic-form {
.ant-form-item {
display: block;
:deep(.ant-form-item-label) {
overflow: inherit;
label::after {
display: none;
}
}
}
}
</style>

View File

@ -5,43 +5,207 @@
style="width: 300px"
allowClear
placeholder="请输入权限名称"
@input="search.search"
/>
<div class="permission-table">
<a-row :gutter="24" class="table-head">
<a-col :span="props.firstWidth">权限名称</a-col
><a-col :span="24 - props.firstWidth">权限操作</a-col>
</a-row>
<div class="table-body" :style="{ 'max-height': props.maxHeight }">
<a-row
:gutter="24"
class="row"
v-for="rowItem in permission.list"
>
<a-col :span="props.firstWidth" class="item-name">
<a-checkbox
v-model:checked="rowItem.checkAll"
:indeterminate="rowItem.indeterminate"
@change="() => permission.selectAllOpions(rowItem)"
>
{{ rowItem.name }}
</a-checkbox>
</a-col>
<a-col :span="24 - props.firstWidth">
<a-checkbox-group
v-model:value="rowItem.checkedList"
:options="rowItem.options"
@change="((checkValue:string[])=>permission.selectOption(rowItem, checkValue))"
/>
</a-col>
</a-row>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { exportPermission_api } from '@/api/system/permission';
const props = defineProps({
value: Array,
});
const props = defineProps<{
value: any[];
firstWidth: number;
maxHeight: string;
}>();
const emits = defineEmits(['update:value']);
const searchValue = ref<string>('');
const search = reactive({
value: '',
searchTimer: null as null | number,
search: () => {
if (search.searchTimer) {
clearTimeout(search.searchTimer);
}
search.searchTimer = setTimeout(() => {
nextTick(() => permission.getList());
search.searchTimer = null;
}, 1000);
},
});
const permission = reactive({
list: [] as permissionType[],
//
getList: () => {
exportPermission_api({ paging: false }).then((resp) => {
permission.list = resp.result as permissionType[]
const params: paramsType = {
paging: false,
};
if (search.value) {
params.terms = [
{ column: 'name$like', value: `%${search.value}%` },
];
}
exportPermission_api(params).then((resp) => {
permission.list = permission.makeList(
props.value,
resp.result as any[],
);
});
},
// /
selectAllOpions: (row: permissionType) => {
const newValue = props.value.filter(
(item) => item.permission !== row.id,
);
row = toRaw(row);
if (row.checkAll) {
row.checkedList = row.options.map((item) => item.value);
newValue.push({
permission: row.id,
actions: row.checkedList,
});
} else {
row.checkedList = [];
}
emits('update:value', newValue);
},
//
selectOption: (row: permissionType, newValue: string[]) => {
const newProp = props.value.filter(
(item) => item.permission !== row.id,
);
if (newValue.length === row.options.length) {
row.checkAll = true;
row.indeterminate = false;
newProp.push({
permission: row.id,
actions: newValue,
});
} else if (newValue.length > 0) {
row.checkAll = false;
row.indeterminate = true;
newProp.push({
permission: row.id,
actions: newValue,
});
}
emits('update:value', newProp);
},
makeList: (checkedValue: any[], sourceList: any[]): permissionType[] => {
console.log(checkedValue);
const result = sourceList.map((item) => {
const checked = checkedValue.find(
(checkedItem) => checkedItem.permission === item.id,
);
const options = item.actions.map((actionItem: any) => ({
label: actionItem.name,
value: actionItem.action,
}));
console.log(item, checked);
return {
id: item.id,
name: item.name,
checkedList: (checked && checked.actions) || [],
checkAll:
(checked &&
item.actions &&
checked?.actions.length === item.actions.length) ||
false,
indeterminate:
(checked &&
item.actions &&
checked.actions.length < item.actions.length) ||
false,
options,
};
}) as permissionType[];
return result;
},
});
permission.getList()
permission.getList();
type permissionType = {
id: string;
name: string;
actions: object[]
}
checkedList: string[];
checkAll: boolean;
indeterminate: boolean;
options: any[];
};
type paramsType = {
paging: boolean;
terms?: object[];
};
</script>
<style scoped></style>
<style lang="less" scoped>
.permission-choose-container {
.permission-table {
margin-top: 12px;
font-size: 14px;
border: 1px solid #d9d9d9;
color: rgba(0, 0, 0, 0.85);
.table-head {
padding: 12px;
background-color: #d9d9d9;
margin: 0 !important;
}
.table-body {
overflow: auto;
.row {
margin: 0 !important;
border-bottom: 1px solid #d9d9d9;
> div {
padding: 8px 12px;
}
.item-name {
display: flex;
align-items: center;
border-right: 1px solid #d9d9d9;
}
}
}
}
}
</style>

View File

View File

@ -19,7 +19,7 @@
<a-button>菜单实例</a-button>
</template>
<template #createTime="slotProps">
{{ slotProps.createTime }}
{{ moment(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss') }}
</template>
<template #action="slotProps">
<a-space :size="16">
@ -30,7 +30,7 @@
type="link"
@click="table.toDetails(slotProps)"
>
<edit-outlined />
<search-outlined />
</a-button>
</a-tooltip>
<a-tooltip>
@ -40,7 +40,7 @@
type="link"
@click="table.toDetails(slotProps)"
>
<edit-outlined />
<plus-circle-outlined />
</a-button>
</a-tooltip>
@ -66,6 +66,13 @@
<script setup lang="ts">
import { getMenuTree_api } from '@/api/system/menu';
import {
SearchOutlined,
DeleteOutlined,
PlusOutlined,
PlusCircleOutlined
} from '@ant-design/icons-vue';
import moment from 'moment';
const router = useRouter();