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

This commit is contained in:
leiqiaochu 2023-09-11 14:35:18 +08:00
commit 03db286aab
19 changed files with 424 additions and 197 deletions

View File

@ -10,7 +10,7 @@ export const updateMeInfo_api = (data:object) => server.put(`/user/detail`,data)
// 修改登录用户密码 // 修改登录用户密码
export const updateMepsd_api = (data:object) => server.put(`/user/passwd`,data); export const updateMepsd_api = (data:object) => server.put(`/user/passwd`,data);
// 第三方账号解绑 // 第三方账号解绑
export const unBind_api = (appId: string) => server.post(`/application/sso/${appId}/unbind/me`); export const unBind_api = (appId: string) => server.post(`/application/sso/${appId}/unbind/me`,[]);
/** /**
* *
* @param type * @param type

View File

@ -36,4 +36,6 @@ export const systemVersion = () => server.get<{edition?: string}>('/system/versi
*/ */
export const queryDashboard = (data: Record<string, any>) => server.post(`/dashboard/_multi`, data) export const queryDashboard = (data: Record<string, any>) => server.post(`/dashboard/_multi`, data)
export const fileUpload = (data: any) => server.post('/file/static', data) export const fileUpload = (data: any) => server.post('/file/static', data)
export const lowCodeUrl = () => server.get('/system/config/low-code')

View File

@ -270,6 +270,11 @@ export const queryDeviceMapping = (deviceId: string, data?: any) => server.post(
*/ */
export const saveDeviceMapping = (deviceId: string, data: any) => server.post(`/edge/operations/${deviceId}/device-mapping-save-batch/invoke`, data) export const saveDeviceMapping = (deviceId: string, data: any) => server.post(`/edge/operations/${deviceId}/device-mapping-save-batch/invoke`, data)
/**
*
*/
export const deleteDeviceMapping = (deviceId: string, data:any) => server.post(`/edge/operations/${deviceId}/device-mapping-delete/invoke`, data)
/** /**
* *
* @param data * @param data
@ -279,7 +284,7 @@ export const getProductListNoPage = (data: any) => server.post('/device/product/
/** /**
* *
*/ */
export const editDevice = (parmas: any) => server.patch('/device-instance', parmas) export const editDevice = (params: any) => server.patch('/device-instance', params)
/** /**
* *

View File

@ -0,0 +1,129 @@
<template>
<j-pro-layout
v-model:collapsed="basicLayout.collapsed"
v-model:openKeys="basicLayout.openKeys"
:breadcrumb="{ routes: breadcrumbs }"
:headerHeight='layout.headerHeight'
:pure="basicLayout.pure"
:selectedKeys="basicLayout.selectedKeys"
v-bind="layoutConf"
@backClick='routerBack'
>
<template #breadcrumbRender="slotProps">
<a
v-if="slotProps.route.index !== 0 && !slotProps.route.isLast"
@click='jump(slotProps.route)'
>
{{ slotProps.route.breadcrumbName }}
</a>
<span v-else style='cursor: default' >{{ slotProps.route.breadcrumbName }}</span>
</template>
<template #rightContentRender>
<div class="right-content">
<AIcon type="QuestionCircleOutlined" @click="toDoc" />
<Notice style="margin: 0 24px" />
<UserInfo />
</div>
</template>
<Iframe />
</j-pro-layout>
</template>
<script lang="ts" name="SinglePage" setup>
import UserInfo from './components/UserInfo.vue';
import Notice from './components/Notice.vue';
import DefaultSetting from '../../../config/config';
import { useMenuStore } from '@/store/menu';
import { clearMenuItem } from 'jetlinks-ui-components/es/ProLayout/util';
import { AccountMenu } from '@/router/menu'
import { useSystem } from '@/store/system';
import { storeToRefs } from 'pinia';
import Iframe from '@/views/iframe/index.vue'
type StateType = {
collapsed: boolean;
openKeys: string[];
selectedKeys: string[];
pure: boolean;
};
const router = useRouter();
const route = useRoute();
const menu = useMenuStore();
const system = useSystem();
const {configInfo,layout, basicLayout} = storeToRefs(system);
const layoutConf = reactive({
theme: DefaultSetting.layout.theme,
siderWidth: layout.value.siderWidth,
logo: DefaultSetting.layout.logo,
title: DefaultSetting.layout.title,
menuData: menu.siderMenus,
// menuData: menu.siderMenus,
splitMenus: true,
});
watchEffect(() => {
layoutConf.theme = configInfo.value.front?.headerTheme || DefaultSetting.layout.theme;
layoutConf.title = configInfo.value.front?.title || DefaultSetting.layout.title;
layoutConf.logo = configInfo.value.front?.logo || DefaultSetting.layout.logo;
})
const components = computed(() => {
const componentName = route.matched[route.matched.length - 1]?.components?.default?.name
if (componentName !== 'BasicLayoutPage') {
return route.matched[route.matched.length - 1]?.components?.default
}
return undefined
})
/**
* 面包屑
*/
const breadcrumbs = computed(() =>
{
const paths = router.currentRoute.value.matched
return paths.map((item, index) => {
return {
index,
isLast: index === (paths.length -1),
path: item.path,
breadcrumbName: (item.meta as any).title || '',
}
})
}
);
const routerBack = () => {
router.go(-1)
}
const jump = (item: any) => {
router.push(item.path)
}
watchEffect(() => {
if (router.currentRoute) {
const paths = router.currentRoute.value.matched
basicLayout.value.selectedKeys = paths.map(item => item.path)
basicLayout.value.openKeys = paths.map(item => item.path)
console.log(paths) //
}
})
const toDoc = () => window.open('http://doc.v2.jetlinks.cn/');
</script>
<style scoped>
.right-content {
margin-right: 24px;
display: flex;
align-items: center;
}
</style>

View File

@ -1,3 +1,4 @@
export { default as BasicLayoutPage } from './BasicLayoutPage.vue' export { default as BasicLayoutPage } from './BasicLayoutPage.vue'
export { default as BlankLayoutPage } from './BlankLayoutPage.vue' export { default as BlankLayoutPage } from './BlankLayoutPage.vue'
export { default as FullPage } from './FullPage.vue' export { default as FullPage } from './FullPage.vue'
export { default as SinglePage } from './SinglePage.vue'

View File

@ -1,6 +1,7 @@
import { BlankLayoutPage, BasicLayoutPage } from 'components/Layout' import { BlankLayoutPage, BasicLayoutPage, SinglePage } from 'components/Layout'
import { isNoCommunity } from '@/utils/utils' import { isNoCommunity } from '@/utils/utils'
import Iframe from '../views/iframe/index.vue' import Iframe from '../views/iframe/index.vue'
import { h } from 'vue'
const pagesComponent = import.meta.glob('../views/**/*.vue'); const pagesComponent = import.meta.glob('../views/**/*.vue');
@ -343,10 +344,12 @@ import { shallowRef } from 'vue'
type Buttons = Array<{ id: string }> type Buttons = Array<{ id: string }>
const hasAppID = (item: { appId?: string, url?: string }): { isApp: boolean, appUrl: string } => { const hasAppID = (item: any): { isApp: boolean, appUrl: string } => {
const isApp = !!item.appId
const isLowCode = !!item.options?.LowCode
return { return {
isApp: !!item.appId, isApp: isApp || isLowCode,
appUrl: `/${item.appId}${item.url}` appUrl: isApp ? `/${item.appId}${item.url}` : item.url
} }
} }
@ -453,6 +456,10 @@ export const handleMenus = (menuData: any[], components: any, level: number = 1)
route.children = route.children ? [...route.children, ...extraRoute] : extraRoute route.children = route.children ? [...route.children, ...extraRoute] : extraRoute
} }
if (item.options?.LowCode && level === 1) {
route.component = () => SinglePage
}
if (detail_components.length) { if (detail_components.length) {
route.children = route.children ? route.children.concat(detail_components) : detail_components route.children = route.children ? route.children.concat(detail_components) : detail_components
} }

View File

@ -5,6 +5,7 @@
visible visible
@ok="handleClick" @ok="handleClick"
@cancel="handleClose" @cancel="handleClose"
:confirmLoading="confirmLoading"
> >
<div class="map-tree"> <div class="map-tree">
<div class="map-tree-top"> <div class="map-tree-top">
@ -99,6 +100,7 @@ const rightList = ref<any[]>([]);
const dataSource = ref<any[]>([]); const dataSource = ref<any[]>([]);
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const confirmLoading = ref(false)
const handleData = (data: any[], type: string) => { const handleData = (data: any[], type: string) => {
data.forEach((item) => { data.forEach((item) => {
item.key = item.id; item.key = item.id;
@ -212,6 +214,7 @@ const _delete = (_key: string) => {
}; };
const handleClick = async () => { const handleClick = async () => {
confirmLoading.value = true
if (!rightList.value.length) { if (!rightList.value.length) {
onlyMessage('请选择采集器', 'warning'); onlyMessage('请选择采集器', 'warning');
} else { } else {
@ -271,6 +274,7 @@ const handleClick = async () => {
} }
} }
} }
confirmLoading.value = false
}; };
const handleClose = () => { const handleClose = () => {
_emits('close'); _emits('close');

View File

@ -280,7 +280,9 @@ const onSave = async () => {
{ deviceId: resq.result?.id, deviceName: formData.name } { deviceId: resq.result?.id, deviceName: formData.name }
] ]
} }
const res = await saveDeviceMapping(instanceStore.current.id, params) if(!instanceStore.current.parentId){
const res = await saveDeviceMapping(instanceStore.current.id, params)
}
const submitData = { const submitData = {
deviceId: instanceStore.current.parentId deviceId: instanceStore.current.parentId
? instanceStore.current.parentId ? instanceStore.current.parentId

View File

@ -113,7 +113,7 @@
<script setup lang="ts"> <script setup lang="ts">
import moment from 'moment'; import moment from 'moment';
import type { ActionsType } from '@/components/Table'; import type { ActionsType } from '@/components/Table';
import {query, unbindDevice, unbindBatchDevice, queryByParent} from '@/api/device/instance'; import {query, unbindDevice, unbindBatchDevice, queryByParent , deleteDeviceMapping} from '@/api/device/instance';
import { useInstanceStore } from '@/store/instance'; import { useInstanceStore } from '@/store/instance';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import BindChildDevice from './BindChildDevice/index.vue'; import BindChildDevice from './BindChildDevice/index.vue';
@ -241,6 +241,10 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
data.id, data.id,
{}, {},
); );
const res = await deleteDeviceMapping(
detail.value.id,
{ids:[data.id]}
)
if (resp.status === 200) { if (resp.status === 200) {
childDeviceRef.value?.reload(); childDeviceRef.value?.reload();
onlyMessage('操作成功!'); onlyMessage('操作成功!');
@ -282,6 +286,10 @@ const handleUnBind = async () => {
_selectedRowKeys.value, _selectedRowKeys.value,
); );
if (resp.status === 200) { if (resp.status === 200) {
const res = await deleteDeviceMapping(
detail.value.id,
{ids:[_selectedRowKeys.value]}
)
onlyMessage('操作成功!'); onlyMessage('操作成功!');
cancelSelect(); cancelSelect();
childDeviceRef.value?.reload(); childDeviceRef.value?.reload();

View File

@ -210,7 +210,6 @@ import PatchMapping from './PatchMapping.vue';
import { onlyMessage } from '@/utils/comm'; import { onlyMessage } from '@/utils/comm';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { usePermissionStore } from '@/store/permission'; import { usePermissionStore } from '@/store/permission';
const columns = [ const columns = [
{ {
title: '名称', title: '名称',
@ -313,6 +312,14 @@ const handleSearch = async (_array: any[]) => {
Object.assign(item, metadataId); Object.assign(item, metadataId);
return item return item
}) })
resp.result?.[0].forEach((item:any)=>{
const differ = array.every((i:any)=>{
return item.metadataId !== i.metadataId
})
if(!differ){
array.push(item)
}
})
// const array = resp.result?.[0].reduce((x: any, y: any) => { // const array = resp.result?.[0].reduce((x: any, y: any) => {
// const metadataId = _metadata.find( // const metadataId = _metadata.find(
// (item: any) => item.metadataId === y.metadataId, // (item: any) => item.metadataId === y.metadataId,
@ -324,6 +331,7 @@ const handleSearch = async (_array: any[]) => {
// } // }
// return x; // return x;
// }, _metadata); // }, _metadata);
console.log(array)
modelRef.dataSource = array; modelRef.dataSource = array;
} }
} }

View File

@ -37,7 +37,7 @@
<AIcon type="EditOutlined" /> <AIcon type="EditOutlined" />
</j-button> </j-button>
</j-popconfirm-modal> </j-popconfirm-modal>
<j-dropdown v-if="myValue === 'rule' && target === 'device' && showReset" :getPopupContainer="(triggerNode) => triggerNode.parentNode"> <j-dropdown v-if="myValue === 'rule' && target === 'device' && showReset" :getPopupContainer="(node) => fullRef || node" placement="bottom">
<span style="width: 20px;" @click.prevent> <span style="width: 20px;" @click.prevent>
<AIcon type="MoreOutlined" /> <AIcon type="MoreOutlined" />
</span> </span>
@ -100,7 +100,13 @@ import { resetRule } from '@/api/device/instance';
import { updata } from '@/api/rule-engine/configuration'; import { updata } from '@/api/rule-engine/configuration';
import { onlyMessage } from '@/utils/comm'; import { onlyMessage } from '@/utils/comm';
import { provide } from 'vue'; import { provide } from 'vue';
import { queryDeviceVirtualProperty } from '@/api/device/instance';
import {
queryProductVirtualProperty
} from '@/api/device/product';
import { useProductStore } from '@/store/product';
const instanceStore = useInstanceStore(); const instanceStore = useInstanceStore();
const productStore = useProductStore();
const PropertySource: { label: string; value: string }[] = isNoCommunity const PropertySource: { label: string; value: string }[] = isNoCommunity
? [ ? [
{ {
@ -133,6 +139,7 @@ type Emit = {
(e: 'update:value', data: Record<string, any>): void; (e: 'update:value', data: Record<string, any>): void;
}; };
const fullRef = inject(FULL_CODE); const fullRef = inject(FULL_CODE);
const showReset = ref(false); const showReset = ref(false);
const props = defineProps({ const props = defineProps({
@ -243,6 +250,32 @@ const cancel = () => {
type.value = props.value?.expands?.type || []; type.value = props.value?.expands?.type || [];
} }
const handleSearch = async () => {
let resp: any = undefined;
if (props.target === 'product') {
resp = await queryProductVirtualProperty(
productStore.current?.id,
props.value?.id,
);
} else {
resp = await queryDeviceVirtualProperty(
instanceStore.current?.productId,
instanceStore.current?.id,
props.value?.id,
);
}
if (resp && resp.status === 200 && resp.result) {
const _triggerProperties = props.value?.expands?.virtualRule?.triggerProperties?.length ? props.value?.expands?.virtualRule?.triggerProperties : resp.result.triggerProperties
updateValue({
source: myValue.value,
virtualRule:{
triggerProperties: _triggerProperties?.length ? _triggerProperties : ['*'],
...resp.result.rule
}
});
}
};
watch( watch(
() => props.value, () => props.value,
() => { () => {
@ -261,6 +294,7 @@ onMounted(()=>{
item === props.value?.id ? showReset.value = true : '' item === props.value?.id ? showReset.value = true : ''
}) })
} }
handleSearch()
}) })
</script> </script>

View File

@ -43,6 +43,7 @@
:actions="getActions(slotProps, 'card')" :actions="getActions(slotProps, 'card')"
:status="slotProps.state?.value" :status="slotProps.state?.value"
:statusText="slotProps.state?.text" :statusText="slotProps.state?.text"
@click="handleView(slotProps.id)"
:statusNames="{ :statusNames="{
online: 'processing', online: 'processing',
offline: 'error', offline: 'error',
@ -63,7 +64,6 @@
<Ellipsis style="width: calc(100% - 100px)"> <Ellipsis style="width: calc(100% - 100px)">
<span <span
style="font-size: 16px; font-weight: 600" style="font-size: 16px; font-weight: 600"
@click.stop="handleView(slotProps.id)"
> >
{{ slotProps.name }} {{ slotProps.name }}
</span> </span>
@ -101,7 +101,7 @@
:key="i" :key="i"
> >
<PermissionButton <PermissionButton
:disabled="o.disabled" :disabled="o.disabled || item.state !== 'online'"
:popConfirm="o.popConfirm" :popConfirm="o.popConfirm"
:tooltip="{ :tooltip="{
...o.tooltip, ...o.tooltip,
@ -470,7 +470,7 @@ const getActions = (
}, },
icon: 'RedoOutlined', icon: 'RedoOutlined',
popConfirm: { popConfirm: {
title: '确认重置密码为P@ssw0rd', title: '确认重置密码为Jetlinks123',
onConfirm: async () => { onConfirm: async () => {
restPassword(data.id).then((resp: any) => { restPassword(data.id).then((resp: any) => {
if (resp.status === 200) { if (resp.status === 200) {

View File

@ -1,6 +1,7 @@
<template> <template>
<page-container> <page-container>
<iframe <iframe
v-if="loading"
:src="iframeUrl" :src="iframeUrl"
frameBorder="0" frameBorder="0"
style="width: 100%; height: calc(100vh - 140px)" style="width: 100%; height: calc(100vh - 140px)"
@ -10,12 +11,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { TOKEN_KEY } from '@/utils/variable'; import { TOKEN_KEY } from '@/utils/variable';
import { LocalStore } from '@/utils/comm'; import { LocalStore, getToken } from '@/utils/comm';
import { getAppInfo_api } from '@/api/system/apply'; import { getAppInfo_api } from '@/api/system/apply';
import { lowCodeUrl } from '@/api/comm'
const iframeUrl = ref<string>(''); const iframeUrl = ref<string>('');
const route = useRoute() const route = useRoute()
const loading = ref(false)
const handle = async (appId: string, url: string) => { const handle = async (appId: string, url: string) => {
const res = await getAppInfo_api(appId); const res = await getAppInfo_api(appId);
let menuUrl: any = url; let menuUrl: any = url;
@ -38,13 +40,26 @@ const handle = async (appId: string, url: string) => {
} }
}; };
const lowCode = () => {
lowCodeUrl().then(res => {
if (res.success && res.result) {
const url = res.result['ui-addr']
iframeUrl.value = url + '/#' + route.path + '?&token=' + getToken()
}
})
}
watchEffect(() => { watchEffect(() => {
const matchedItem: any = route.matched?.[0] const matchedItem: any = route.matched?.[0]
if (matchedItem?.meta?.isApp) { if (matchedItem?.meta?.isApp) {
const params = route.path.split('/')?.[1]; const params = route.path.split('/')?.[1];
const url = route.path.split('/').slice(2).join('/'); if (params === 'preview') {
handle(params, url); lowCode()
} else {
loading.value = true
const url = route.path.split('/').slice(2).join('/');
handle(params, url);
}
} }
}); });
</script> </script>

View File

@ -283,6 +283,11 @@
cluster cluster
.configuration .configuration
.port .port
"
:options="
portOptionsIndex[
index
]
" "
placeholder="请选择本地端口" placeholder="请选择本地端口"
allowClear allowClear
@ -291,7 +296,7 @@
filterPortOption filterPortOption
" "
> >
<j-select-option <!-- <j-select-option
v-for="i in getPortList( portOptionsIndex[ v-for="i in getPortList( portOptionsIndex[
index index
], cluster ], cluster
@ -300,7 +305,7 @@
:value="i.value" :value="i.value"
> >
{{ i.label }} {{ i.label }}
</j-select-option> </j-select-option> -->
</j-select> </j-select>
</j-form-item> </j-form-item>
</j-col> </j-col>
@ -1231,11 +1236,11 @@ const filterPortOption = (input: string, option: any) => {
return JSON.stringify(option.label).indexOf(input) >= 0; return JSON.stringify(option.label).indexOf(input) >= 0;
}; };
const getPortList = (list: any[], id: string) => { // const getPortList = (list: any[], id: string) => {
const keys = dynamicValidateForm?.cluster?.map?.(item => item.configuration?.port) || [] // const keys = dynamicValidateForm?.cluster?.map?.(item => item.configuration?.port) || []
// console.log(dynamicValidateForm?.cluster, id, keys) // // console.log(dynamicValidateForm?.cluster, id, keys)
return (list || []).filter(item => item.value === id || !keys.includes(item.value) ) // return (list || []).filter(item => item.value === id || !keys.includes(item.value) )
} // }
const filterConfigByType = (data: any[], type: string) => { const filterConfigByType = (data: any[], type: string) => {
let _temp = type; let _temp = type;

View File

@ -130,7 +130,7 @@
</template> </template>
</MediaTool> </MediaTool>
</div> </div>
<Preset :data="data" @refresh="onRefresh" /> <Preset v-if='data.ptzType.value ===0 || data.ptzType.value === 1' :data="data" @refresh="onRefresh" />
</div> </div>
</div> </div>
<template #footer> <template #footer>

View File

@ -1,176 +1,180 @@
<template> <template>
<page-container> <page-container>
<div class="manager-container"> <FullPage>
<div class="left"> <div class="manager-container">
<j-input-search <div class="left">
v-model:value="leftData.searchValue" <j-input-search
placeholder="请输入" v-model:value="leftData.searchValue"
style="margin-bottom: 24px" placeholder="请输入"
/> style="margin-bottom: 24px"
<!-- 使用v-if用于解决异步加载数据后不展开的问题 --> />
<j-tree <!-- 使用v-if用于解决异步加载数据后不展开的问题 -->
v-if="leftData.treeData.length > 0" <j-tree
defaultExpandAll v-if="leftData.treeData.length > 0"
:tree-data="leftData.treeData" defaultExpandAll
v-model:selectedKeys="leftData.selectedKeys" :tree-data="leftData.treeData"
@select="onSelect" v-model:selectedKeys="leftData.selectedKeys"
:showLine="{ showLeafIcon: false }" @select="onSelect"
:show-icon="true" :showLine="{ showLeafIcon: false }"
> :show-icon="true"
<template #title="{ dataRef }">
<div
v-if="dataRef.root"
style="
justify-content: space-between;
display: flex;
align-items: center;
width: 200px;
"
>
<span>
{{ dataRef.title }}
</span>
<AIcon
type="PlusOutlined"
style="color: #1d39c4"
@click="addTable"
/>
</div>
<span v-else>
{{ dataRef.title }}
</span>
</template>
</j-tree>
</div>
<div class="right">
<div class="btns">
<j-button type="primary" @click="clickSave">保存</j-button>
</div>
<j-form ref="formRef" :model="table">
<j-table
:columns="columns"
:dataSource="table.data"
:pagination="false"
:scroll="{ y: 500 }"
> >
<template #bodyCell="{ column, record, index }"> <template #title="{ dataRef }">
<template v-if="column.key === 'name'"> <Ellipsis>
<j-form-item <div
:name="['data', index, 'name']" v-if="dataRef.root"
:rules="[ style="
{ justify-content: space-between;
max: 64, display: flex;
message: '最多可输入64个字符', align-items: center;
}, width: 200px;
{ "
required: true,
message: '请输入名称',
},
]"
> >
<j-input <span>
:disabled="record.old_id" {{ dataRef.title }}
v-model:value="record.name" </span>
placeholder="请输入名称" <AIcon
type="PlusOutlined"
style="color: #1d39c4"
@click="addTable"
/> />
</j-form-item> </div>
</template> <span v-else>
<template v-else-if="column.key === 'type'"> {{ dataRef.title }}
<j-form-item </span>
:name="['data', index, 'type']" </Ellipsis>
:rules="[
{
max: 64,
message: '最多可输入64个字符',
},
{
required: true,
message: '请输入类型',
},
]"
>
<j-input
v-model:value="record.type"
placeholder="请输入类型"
/>
</j-form-item>
</template>
<template v-else-if="column.key === 'length'">
<j-form-item :name="['data', index, 'length']">
<j-input-number
v-model:value="record.length"
:min="0"
:max="99999"
style="width: 100%"
/>
</j-form-item>
</template>
<template v-else-if="column.key === 'scale'">
<j-form-item :name="['data', index, 'scale']">
<j-input-number
v-model:value="record.scale"
:min="0"
:max="99999"
style="width: 100%"
/>
</j-form-item>
</template>
<template v-else-if="column.key === 'notnull'">
<j-form-item
:name="['data', index, 'notnull']"
:rules="[
{
required: true,
message: '请选择是否不能为空',
},
]"
>
<j-radio-group
v-model:value="record.notnull"
button-style="solid"
>
<j-radio-button :value="true"
></j-radio-button
>
<j-radio-button :value="false"
></j-radio-button
>
</j-radio-group>
</j-form-item>
</template>
<template v-else-if="column.key === 'comment'">
<j-form-item :name="['data', index, 'comment']">
<j-input
v-model:value="record.comment"
placeholder="请输入说明"
/>
</j-form-item>
</template>
<template v-else-if="column.key === 'action'">
<PermissionButton
hasPermission="system/DataSource:delete"
type="link"
:tooltip="{ title: '删除' }"
:danger="true"
:popConfirm="{
title: `确认删除`,
onConfirm: () =>
clickDel(record, index),
}"
:disabled="record.status"
>
<AIcon type="DeleteOutlined" />
</PermissionButton>
</template>
</template> </template>
</j-table> </j-tree>
</j-form> </div>
<div class="right">
<div class="btns">
<j-button type="primary" @click="clickSave">保存</j-button>
</div>
<j-form ref="formRef" :model="table">
<j-table
:columns="columns"
:dataSource="table.data"
:pagination="false"
:scroll="{ y: 500 }"
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'name'">
<j-form-item
:name="['data', index, 'name']"
:rules="[
{
max: 64,
message: '最多可输入64个字符',
},
{
required: true,
message: '请输入名称',
},
]"
>
<j-input
:disabled="record.old_id"
v-model:value="record.name"
placeholder="请输入名称"
/>
</j-form-item>
</template>
<template v-else-if="column.key === 'type'">
<j-form-item
:name="['data', index, 'type']"
:rules="[
{
max: 64,
message: '最多可输入64个字符',
},
{
required: true,
message: '请输入类型',
},
]"
>
<j-input
v-model:value="record.type"
placeholder="请输入类型"
/>
</j-form-item>
</template>
<template v-else-if="column.key === 'length'">
<j-form-item :name="['data', index, 'length']">
<j-input-number
v-model:value="record.length"
:min="0"
:max="99999"
style="width: 100%"
/>
</j-form-item>
</template>
<template v-else-if="column.key === 'scale'">
<j-form-item :name="['data', index, 'scale']">
<j-input-number
v-model:value="record.scale"
:min="0"
:max="99999"
style="width: 100%"
/>
</j-form-item>
</template>
<template v-else-if="column.key === 'notnull'">
<j-form-item
:name="['data', index, 'notnull']"
:rules="[
{
required: true,
message: '请选择是否不能为空',
},
]"
>
<j-radio-group
v-model:value="record.notnull"
button-style="solid"
>
<j-radio-button :value="true"
></j-radio-button
>
<j-radio-button :value="false"
></j-radio-button
>
</j-radio-group>
</j-form-item>
</template>
<template v-else-if="column.key === 'comment'">
<j-form-item :name="['data', index, 'comment']">
<j-input
v-model:value="record.comment"
placeholder="请输入说明"
/>
</j-form-item>
</template>
<template v-else-if="column.key === 'action'">
<PermissionButton
hasPermission="system/DataSource:delete"
type="link"
:tooltip="{ title: '删除' }"
:danger="true"
:popConfirm="{
title: `确认删除`,
onConfirm: () =>
clickDel(record, index),
}"
:disabled="record.status"
>
<AIcon type="DeleteOutlined" />
</PermissionButton>
</template>
</template>
</j-table>
</j-form>
<j-button class="add-row" @click="addRow"> <j-button class="add-row" @click="addRow">
<AIcon type="PlusOutlined" /> 新增行 <AIcon type="PlusOutlined" /> 新增行
</j-button> </j-button>
</div>
</div> </div>
</div> </FullPage>
<j-modal <j-modal
:visible="true" :visible="true"
v-if="dialog.visible" v-if="dialog.visible"
@ -495,12 +499,15 @@ const checkName = (_: any, value: any) =>
padding: 24px; padding: 24px;
background-color: #fff; background-color: #fff;
display: flex; display: flex;
min-height: 500px; height: 100%;
.left { .left {
flex-basis: 280px; flex-basis: 280px;
padding: 0 24px; padding: 0 24px;
box-sizing: border-box; box-sizing: border-box;
width:300px;
height:100%;
overflow-y: auto;
} }
.right { .right {
width: calc(100% - 280px); width: calc(100% - 280px);

View File

@ -8,7 +8,7 @@
> >
<div class="alert"> <div class="alert">
<AIcon type="InfoCircleOutlined" /> <AIcon type="InfoCircleOutlined" />
通过角色控制{{ name }}的所有的通知方式可被哪些用户订阅 通过角色控制哪些用户可以订阅{{ name }}下所有的通知方式
</div> </div>
<Role v-model="_selectedRowKeys" :gridColumn="2" /> <Role v-model="_selectedRowKeys" :gridColumn="2" />
</j-modal> </j-modal>

View File

@ -63,7 +63,7 @@
<template v-if="current === 4"> <template v-if="current === 4">
<div class="alert"> <div class="alert">
<AIcon type="InfoCircleOutlined" /> <AIcon type="InfoCircleOutlined" />
已规定固定收信人的模板在当前页面将被过滤 通过角色控制哪些用户可以订阅从{{ name }}接收到{{ showName }}通知
</div> </div>
<Role type="add" v-model="formModel.grant.role.idList" /> <Role type="add" v-model="formModel.grant.role.idList" />
</template> </template>

View File

@ -109,7 +109,7 @@
</j-button> </j-button>
</j-form-item> </j-form-item>
</j-form> </j-form>
<div class="other"> <div class="other" v-if="bindings.length">
<j-divider plain> <j-divider plain>
<div class="other-text"> <div class="other-text">
其他登录方式 其他登录方式
@ -245,7 +245,7 @@ const codeUrl = ref('');
const codeConfig = ref(false); const codeConfig = ref(false);
const loading = ref(false); const loading = ref(false);
const bindings = ref<any[]>(); const bindings = ref<any[]>([]);
// const basis = ref<any>({}); // const basis = ref<any>({});
const defaultImg = getImage('/apply/internal-standalone.png'); const defaultImg = getImage('/apply/internal-standalone.png');