Merge remote-tracking branch 'origin/master'

pull/48/head
shao1121353141 2023-09-13 21:55:53 +08:00
commit b0bc28c2d2
8 changed files with 160 additions and 104 deletions

View File

@ -17,6 +17,7 @@
*/ */
package cn.topiam.employee.application; package cn.topiam.employee.application;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -86,6 +87,19 @@ public interface ApplicationService {
*/ */
String getBase64Icon(); String getBase64Icon();
/**
*
*
* @param name {@link String}
* @param icon {@link String}
* @param remark {@link String}
* @return {@link Long} ID
*/
@Transactional(rollbackFor = Exception.class)
default String create(String name, String icon, String remark) {
return create(name, icon, remark, new ArrayList<>());
}
/** /**
* *
* *

View File

@ -119,7 +119,7 @@ public class AppRepositoryCustomizedImpl implements AppRepositoryCustomized {
*/ */
public Page<AppEntity> getAppList(AppQuery appQuery, Pageable pageable) { public Page<AppEntity> getAppList(AppQuery appQuery, Pageable pageable) {
//@formatter:off //@formatter:off
StringBuilder builder = new StringBuilder("SELECT DISTINCT app.* FROM app INNER JOIN app_group_association `group` ON app.id_ = `group`.app_id AND app.is_deleted = 0"); StringBuilder builder = new StringBuilder("SELECT DISTINCT app.* FROM app LEFT JOIN app_group_association `group` ON app.id_ = `group`.app_id AND app.is_deleted = 0");
//应用名称 //应用名称
if (StringUtils.isNoneBlank(appQuery.getName())) { if (StringUtils.isNoneBlank(appQuery.getName())) {

View File

@ -361,7 +361,7 @@ const IdentityProvider = () => {
const { styles } = useStyle(prefixCls); const { styles } = useStyle(prefixCls);
useMount(async () => { useMount(async () => {
if (!type || !(type.toUpperCase() in IdentityProviderCategory)) { if (!type || !IdentityProviderCategory[type]) {
setTabActiveKey(IdentityProviderCategory.social); setTabActiveKey(IdentityProviderCategory.social);
history.push({ history.push({
pathname: location.pathname, pathname: location.pathname,

View File

@ -17,10 +17,7 @@
*/ */
package cn.topiam.employee.console.service.app.impl; package cn.topiam.employee.console.service.app.impl;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import cn.topiam.employee.common.entity.app.AppGroupAssociationEntity; import cn.topiam.employee.common.entity.app.AppGroupAssociationEntity;
import cn.topiam.employee.common.repository.app.AppGroupAssociationRepository; import cn.topiam.employee.common.repository.app.AppGroupAssociationRepository;
@ -52,6 +49,7 @@ import cn.topiam.employee.support.util.BeanUtils;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY; import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY;
import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME; import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME;
@ -93,8 +91,13 @@ public class AppServiceImpl implements AppService {
public AppCreateResult createApp(AppCreateParam param) { public AppCreateResult createApp(AppCreateParam param) {
ApplicationService applicationService = applicationServiceLoader ApplicationService applicationService = applicationServiceLoader
.getApplicationService(param.getTemplate()); .getApplicationService(param.getTemplate());
String appId = applicationService.create(param.getName(), param.getIcon(), String appId;
param.getRemark(), param.getGroupIds()); if (!CollectionUtils.isEmpty(param.getGroupIds())) {
appId = applicationService.create(param.getName(), param.getIcon(), param.getRemark(),
param.getGroupIds());
} else {
appId = applicationService.create(param.getName(), param.getIcon(), param.getRemark());
}
AuditContext.setTarget(Target.builder().id(appId).type(TargetType.APPLICATION).build()); AuditContext.setTarget(Target.builder().id(appId).type(TargetType.APPLICATION).build());
return new AppCreateResult(appId); return new AppCreateResult(appId);
} }

View File

@ -16,54 +16,25 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import type { ActionType } from '@ant-design/pro-components'; import type { ActionType } from '@ant-design/pro-components';
import { PageContainer, ProCard, ProList } from '@ant-design/pro-components'; import {
import { Alert, App, Avatar, Badge, Card, Input, Typography } from 'antd'; PageContainer,
ProCard,
ProFormText,
ProList,
QueryFilter,
} from '@ant-design/pro-components';
import { App, Avatar, Badge, Card, Typography } from 'antd';
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import type { AppList } from './data.d'; import { AppList, InitLoginType } from './data.d';
import { InitLoginType } from './data.d'; import { getAppGroupList, queryAppList } from './service';
import { queryAppList } from './service';
import { useIntl } from '@@/exports'; import { useIntl } from '@@/exports';
import useStyle from './style'; import useStyle from './style';
import classnames from 'classnames'; import classnames from 'classnames';
import { useAsyncEffect } from 'ahooks';
import { SpinProps } from 'antd/es/spin';
const { Paragraph } = Typography; const { Paragraph } = Typography;
const prefixCls = 'topiam-app-list'; const prefixCls = 'topiam-app-list';
const CardList = () => {
const intl = useIntl();
const { styles } = useStyle(prefixCls);
const [activeKey, setActiveKey] = useState<React.Key | undefined>('tab1');
const { message } = App.useApp();
const actionRef = useRef<ActionType>();
const [searchParams, setSearchParams] = useState<{ name: string }>();
const content = (
<div style={{ textAlign: 'center' }}>
<Input.Search
placeholder={intl.formatMessage({ id: 'pages.application.search' })}
enterButton={intl.formatMessage({ id: 'pages.application.search.enter_button' })}
size="large"
style={{ maxWidth: 522, width: '100%' }}
onSearch={(value) => {
setSearchParams({ name: value });
actionRef.current?.reload();
}}
/>
</div>
);
const initSso = (idpInitUrl: string) => {
const div = window.document.createElement('div');
div.innerHTML =
"<form action='" +
idpInitUrl +
"' method='POST' name='auto_submit_form' style='display:none'>";
document.body.appendChild(div);
// eslint-disable-next-line @typescript-eslint/dot-notation
document.forms['auto_submit_form'].setAttribute('target', '_blank');
// eslint-disable-next-line @typescript-eslint/dot-notation
document.forms['auto_submit_form'].submit();
document.body.removeChild(div);
};
const renderBadge = (count: number, active = false) => { const renderBadge = (count: number, active = false) => {
return ( return (
<Badge <Badge
@ -78,28 +49,65 @@ const CardList = () => {
); );
}; };
const CardList = () => {
const intl = useIntl();
const { styles } = useStyle(prefixCls);
// 当前组
const [currentGroup, setCurrentGroup] = useState<React.Key>();
const { message } = App.useApp();
const actionRef = useRef<ActionType>();
const [searchParams, setSearchParams] = useState<Record<string, any>>();
const [loading, setLoading] = useState<boolean | SpinProps | undefined>(false);
const [items, setItems] = useState<{ key: string; label: React.JSX.Element }[]>([]);
const initSso = (idpInitUrl: string) => {
const div = window.document.createElement('div');
div.innerHTML =
"<form action='" +
idpInitUrl +
"' method='POST' name='auto_submit_form' style='display:none'>";
document.body.appendChild(div);
// eslint-disable-next-line @typescript-eslint/dot-notation
document.forms['auto_submit_form'].setAttribute('target', '_blank');
// eslint-disable-next-line @typescript-eslint/dot-notation
document.forms['auto_submit_form'].submit();
document.body.removeChild(div);
};
useAsyncEffect(async () => {
setLoading(true);
const { result, success } = await getAppGroupList().finally(() => {
setLoading(false);
});
if (success && result) {
let data: { key: string; label: React.JSX.Element }[] = [];
result.forEach((item) => {
data.push({
key: item.id,
label: (
<span>
{item.name}
{renderBadge(item.appCount, currentGroup === item.id)}
</span>
),
});
});
setItems(data);
// 如果有分组,取第一个分组
if (data.length > 0) {
setSearchParams({ groupId: data[0].key });
actionRef.current?.reload();
}
// 手动请求
else {
actionRef.current?.reload();
}
}
}, []);
return ( return (
<div className={styles}> <div className={styles}>
<PageContainer <PageContainer className={classnames(`${prefixCls}`)}>
className={classnames(`${prefixCls}`)}
tabList={[
{
tab: '应用列表',
key: 'list',
},
{
tab: '应用账号',
key: 'account',
},
]}
>
<Alert
banner
type={'info'}
message={intl.formatMessage({ id: 'pages.application.alert' })}
showIcon
/>
<br />
<ProList<AppList> <ProList<AppList>
rowKey="id" rowKey="id"
split split
@ -111,32 +119,34 @@ const CardList = () => {
xl: 4, xl: 4,
xxl: 5, xxl: 5,
}} }}
loading={loading}
onLoadingChange={(loading) => {
setLoading(loading);
}}
manualRequest
request={queryAppList} request={queryAppList}
pagination={{}} toolbar={
toolbar={{ items.length > 0
? {
menu: { menu: {
type: 'tab', type: 'tab',
activeKey, activeKey: currentGroup,
items: [ items: items,
{
key: 'tab1',
label: <span>{renderBadge(99, activeKey === 'tab1')}</span>,
},
{
key: 'tab2',
label: <span>{renderBadge(32, activeKey === 'tab2')}</span>,
},
],
onChange(key) { onChange(key) {
setActiveKey(key); if (key) {
setCurrentGroup(key);
setSearchParams((values) => {
return { ...values, groupId: key };
});
actionRef.current?.reload();
}
}, },
}, },
}} }
: {}
}
params={searchParams} params={searchParams}
actionRef={actionRef} actionRef={actionRef}
tableExtraRender={() => {
return <ProCard>{content}</ProCard>;
}}
renderItem={(item: AppList) => { renderItem={(item: AppList) => {
return ( return (
item && item &&
@ -147,8 +157,11 @@ const CardList = () => {
hoverable hoverable
bordered={false} bordered={false}
onClick={async () => { onClick={async () => {
if (item.initLoginType === InitLoginType.PORTAL_OR_APP_INIT_SSO) { if (
initSso(item.initLoginUrl); item.initLoginType === InitLoginType.portal_or_app_init_sso &&
item?.initLoginUrl
) {
initSso(item?.initLoginUrl);
return; return;
} }
message.warning( message.warning(
@ -175,6 +188,33 @@ const CardList = () => {
) )
); );
}} }}
tableExtraRender={() => {
return (
<ProCard bodyStyle={{ padding: 0 }}>
<QueryFilter
layout="horizontal"
onFinish={(values) => {
setSearchParams({ ...searchParams, ...values });
actionRef.current?.reload();
return Promise.resolve();
}}
onReset={() => {
if (items.length > 0) {
setSearchParams({ groupId: currentGroup });
} else {
setSearchParams({});
}
actionRef.current?.reload();
}}
>
<ProFormText
name="name"
label={intl.formatMessage({ id: 'pages.application.search.name' })}
/>
</QueryFilter>
</ProCard>
);
}}
/> />
</PageContainer> </PageContainer>
</div> </div>

View File

@ -22,8 +22,8 @@ export type AppList = {
template: string; template: string;
icon?: string; icon?: string;
name: string; name: string;
initLoginType: InitLoginType; initLoginType: InitLoginType | string;
initLoginUrl: string; initLoginUrl?: string;
description: string; description: string;
}; };

View File

@ -16,8 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
export default { export default {
'pages.application.search': '请输入', 'pages.application.search.name': '应用名称',
'pages.application.search.enter_button': '搜索', 'pages.application.tab.list': '应用列表',
'pages.application.alert': '请点击下方的应用进行单点登录。若希望修改应用内容,请联系管理员。', 'pages.application.tab.account': '应用账号',
'pages.application.init.warning': '仅允许应用发起', 'pages.application.init.warning': '仅允许应用发起',
}; };

View File

@ -24,7 +24,6 @@ const useStyles = createStyles(({ token, css, prefixCls }, prop) => {
border: none; border: none;
border-radius: ${token.borderRadius}; border-radius: ${token.borderRadius};
.${prefixCls}-pro-table-list-toolbar-container { .${prefixCls}-pro-table-list-toolbar-container {
margin-bottom: 10px;
} }
&-item-card { &-item-card {