feat:响应式布局调整
parent
93900308e0
commit
9d0edbfa00
|
|
@ -399,7 +399,7 @@ export const MeetingCreateDrawer: React.FC<MeetingCreateDrawerProps> = ({
|
|||
onClose={onCancel}
|
||||
width="min(960px, calc(100vw - 48px))"
|
||||
forceRender
|
||||
destroyOnClose={false}
|
||||
destroyOnHidden={false}
|
||||
placement="right"
|
||||
closable={false}
|
||||
footer={
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@
|
|||
min-width: 0;
|
||||
min-height: 32px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.data-list-panel__table-container {
|
||||
|
|
@ -240,6 +241,22 @@
|
|||
border-bottom-color: rgba(22, 119, 255, 0.18) !important;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.data-list-panel__toolbar {
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.data-list-panel__right-actions {
|
||||
flex: 1 1 420px;
|
||||
}
|
||||
|
||||
.data-list-panel__right-actions .ant-space {
|
||||
justify-content: flex-end;
|
||||
row-gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.data-list-panel {
|
||||
padding: 12px;
|
||||
|
|
@ -258,7 +275,15 @@
|
|||
.data-list-panel__right-actions,
|
||||
.data-list-panel__right-actions .ant-space,
|
||||
.data-list-panel__right-actions .ant-input-affix-wrapper,
|
||||
.data-list-panel__right-actions .ant-select {
|
||||
.data-list-panel__right-actions .ant-input,
|
||||
.data-list-panel__right-actions .ant-select,
|
||||
.data-list-panel__right-actions .ant-picker,
|
||||
.data-list-panel__right-actions .ant-input-number,
|
||||
.data-list-panel__right-actions .ant-btn {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.data-list-panel__right-actions .ant-space {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,11 @@
|
|||
}
|
||||
|
||||
.selection-action {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--link-color);
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ function ListTable<T extends Record<string, any>>({
|
|||
showSizeChanger: { showSearch: false },
|
||||
showQuickJumper: true,
|
||||
},
|
||||
scroll = { x: 1200 },
|
||||
scroll = { x: "max-content" },
|
||||
onRowClick,
|
||||
selectedRow,
|
||||
loading = false,
|
||||
|
|
@ -49,7 +49,7 @@ function ListTable<T extends Record<string, any>>({
|
|||
}: ListTableProps<T>) {
|
||||
const mergedScroll = React.useMemo(() => {
|
||||
return {
|
||||
x: 1200,
|
||||
x: "max-content",
|
||||
...scroll,
|
||||
};
|
||||
}, [scroll]);
|
||||
|
|
@ -95,9 +95,9 @@ function ListTable<T extends Record<string, any>>({
|
|||
已选择 <span className="count-highlight">{totalCount || total}</span> 项
|
||||
</span>
|
||||
{onClearSelection && (
|
||||
<a onClick={onClearSelection} className="selection-action">
|
||||
<button type="button" onClick={onClearSelection} className="selection-action">
|
||||
清除选择
|
||||
</a>
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
) : selectedRowKeys.length > 0 ? (
|
||||
|
|
@ -106,14 +106,14 @@ function ListTable<T extends Record<string, any>>({
|
|||
已选择 <span className="count-highlight">{selectedRowKeys.length}</span> 项
|
||||
</span>
|
||||
{onSelectAllPages && selectedRowKeys.length < (totalCount || total) && (
|
||||
<a onClick={onSelectAllPages} className="selection-action">
|
||||
<button type="button" onClick={onSelectAllPages} className="selection-action">
|
||||
选择全部 {totalCount || total} 项
|
||||
</a>
|
||||
</button>
|
||||
)}
|
||||
{onClearSelection && (
|
||||
<a onClick={onClearSelection} className="selection-action">
|
||||
<button type="button" onClick={onClearSelection} className="selection-action">
|
||||
清除
|
||||
</a>
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -201,6 +201,24 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section-card__title-wrap {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.section-card__title {
|
||||
align-items: flex-start;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.section-card__title::before {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.section-card__description {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.section-card__extra {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,14 @@
|
|||
--meeting-progress-color: #1677ff;
|
||||
--app-form-drawer-width: 600px;
|
||||
--app-form-drawer-max-width: calc(100vw - 48px);
|
||||
--app-breakpoint-sm: 576px;
|
||||
--app-breakpoint-md: 768px;
|
||||
--app-breakpoint-lg: 992px;
|
||||
--app-breakpoint-xl: 1200px;
|
||||
--app-page-padding: 16px;
|
||||
--app-page-padding-mobile: 12px;
|
||||
--app-control-min-width: 160px;
|
||||
--app-control-wide-width: 220px;
|
||||
}
|
||||
|
||||
:root[data-theme="minimal"] {
|
||||
|
|
@ -365,6 +373,15 @@ body::after {
|
|||
padding: 8px 4px 4px;
|
||||
}
|
||||
|
||||
.app-responsive-form-row {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.app-responsive-form-row .ant-col,
|
||||
.app-responsive-form-field {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ant-drawer:has(.app-page__drawer-footer) .ant-drawer-content-wrapper,
|
||||
.ant-drawer:has(.screen-saver-drawer__footer) .ant-drawer-content-wrapper,
|
||||
.meeting-create-drawer-root .ant-drawer-content-wrapper {
|
||||
|
|
@ -928,6 +945,15 @@ body::after {
|
|||
.app-page__content-toolbar-filters {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.app-page__content-toolbar .ant-space,
|
||||
.app-page__content-toolbar .ant-input,
|
||||
.app-page__content-toolbar .ant-input-affix-wrapper,
|
||||
.app-page__content-toolbar .ant-select,
|
||||
.app-page__content-toolbar .ant-picker,
|
||||
.app-page__content-toolbar .ant-input-number {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1351,6 +1377,14 @@ body::after {
|
|||
--meeting-progress-color: #1677ff;
|
||||
--app-form-drawer-width: 600px;
|
||||
--app-form-drawer-max-width: calc(100vw - 48px);
|
||||
--app-breakpoint-sm: 576px;
|
||||
--app-breakpoint-md: 768px;
|
||||
--app-breakpoint-lg: 992px;
|
||||
--app-breakpoint-xl: 1200px;
|
||||
--app-page-padding: 16px;
|
||||
--app-page-padding-mobile: 12px;
|
||||
--app-control-min-width: 160px;
|
||||
--app-control-wide-width: 220px;
|
||||
}
|
||||
|
||||
:root[data-theme="tech"] {
|
||||
|
|
@ -1385,6 +1419,14 @@ body::after {
|
|||
--meeting-progress-color: #1677ff;
|
||||
--app-form-drawer-width: 600px;
|
||||
--app-form-drawer-max-width: calc(100vw - 48px);
|
||||
--app-breakpoint-sm: 576px;
|
||||
--app-breakpoint-md: 768px;
|
||||
--app-breakpoint-lg: 992px;
|
||||
--app-breakpoint-xl: 1200px;
|
||||
--app-page-padding: 16px;
|
||||
--app-page-padding-mobile: 12px;
|
||||
--app-control-min-width: 160px;
|
||||
--app-control-wide-width: 220px;
|
||||
}
|
||||
|
||||
html,
|
||||
|
|
|
|||
|
|
@ -54,6 +54,11 @@
|
|||
gap: 16px;
|
||||
}
|
||||
|
||||
.mobile-menu-trigger {
|
||||
display: none;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.main-sider {
|
||||
position: relative;
|
||||
height: calc(100vh - 64px);
|
||||
|
|
@ -270,6 +275,106 @@
|
|||
background: var(--app-bg-layout);
|
||||
}
|
||||
|
||||
.main-sider-backdrop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.main-header {
|
||||
gap: 8px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.mobile-menu-trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--app-text-main);
|
||||
}
|
||||
|
||||
.app-header-logo {
|
||||
flex: 1 1 auto;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.app-header-logo img {
|
||||
height: 32px;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
.app-header-logo-title {
|
||||
max-width: 120px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
flex: 0 0 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.header-right > .ant-space {
|
||||
gap: 12px !important;
|
||||
}
|
||||
|
||||
.header-right .ant-avatar + span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main-shell {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.main-sider {
|
||||
position: fixed !important;
|
||||
top: 64px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 30;
|
||||
width: 180px !important;
|
||||
min-width: 180px !important;
|
||||
max-width: 180px !important;
|
||||
height: calc(100vh - 64px);
|
||||
border-top: none;
|
||||
box-shadow: 8px 0 24px rgba(15, 23, 42, 0.16);
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.24s ease;
|
||||
}
|
||||
|
||||
.main-layout--mobile-sider-open .main-sider {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.main-layout--mobile .main-sider--collapsed {
|
||||
width: 0 !important;
|
||||
min-width: 0 !important;
|
||||
max-width: 0 !important;
|
||||
flex: 0 0 0 !important;
|
||||
}
|
||||
|
||||
.main-layout--mobile .main-sider .collapsedCenter {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main-sider .ant-layout-sider-children {
|
||||
height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
.main-layout--mobile-sider-open .main-sider-backdrop {
|
||||
position: fixed;
|
||||
inset: 64px 0 0;
|
||||
z-index: 25;
|
||||
display: block;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: rgba(15, 23, 42, 0.28);
|
||||
}
|
||||
|
||||
.main-content-layout {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-theme="tech"] .main-header,
|
||||
:root[data-theme="tech"] .main-sider,
|
||||
:root[data-theme="tech"] .main-menu,
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ function findActiveMenu(nodes: PermissionMenuNode[], path: string, parentKeys: s
|
|||
export default function AppLayout() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [isMobileLayout, setIsMobileLayout] = useState(false);
|
||||
const [menus, setMenus] = useState<SysPermission[]>([]);
|
||||
const [availableTenants, setAvailableTenants] = useState<TenantInfo[]>([]);
|
||||
const [currentTenantId, setCurrentTenantId] = useState<number | null>(null);
|
||||
|
|
@ -320,6 +321,30 @@ export default function AppLayout() {
|
|||
setOpenKeys((prev) => Array.from(new Set([...prev, ...activeMenu.parentKeys])));
|
||||
}, [activeMenu]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const mediaQuery = window.matchMedia("(max-width: 768px)");
|
||||
const syncMobileLayout = () => {
|
||||
setIsMobileLayout(mediaQuery.matches);
|
||||
if (mediaQuery.matches) {
|
||||
setCollapsed(true);
|
||||
}
|
||||
};
|
||||
|
||||
syncMobileLayout();
|
||||
mediaQuery.addEventListener("change", syncMobileLayout);
|
||||
return () => mediaQuery.removeEventListener("change", syncMobileLayout);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobileLayout) {
|
||||
setCollapsed(true);
|
||||
}
|
||||
}, [isMobileLayout, location.pathname]);
|
||||
|
||||
const userMenuItems: MenuProps["items"] = useMemo(() => {
|
||||
const items: NonNullable<MenuProps["items"]> = [
|
||||
{
|
||||
|
|
@ -382,14 +407,36 @@ export default function AppLayout() {
|
|||
</div>
|
||||
);
|
||||
|
||||
const layoutClassName = [
|
||||
"main-layout",
|
||||
isMobileLayout ? "main-layout--mobile" : "",
|
||||
isMobileLayout && !collapsed ? "main-layout--mobile-sider-open" : "",
|
||||
].filter(Boolean).join(" ");
|
||||
|
||||
return (
|
||||
<Layout className="main-layout">
|
||||
<Layout className={layoutClassName}>
|
||||
<Header className="main-header">
|
||||
<Button
|
||||
type="text"
|
||||
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
shape="circle"
|
||||
onClick={() => setCollapsed((prev) => !prev)}
|
||||
className="mobile-menu-trigger"
|
||||
aria-label={collapsed ? "展开菜单" : "收起菜单"}
|
||||
/>
|
||||
{renderLogo()}
|
||||
<div className="header-right">{headerRightTools}</div>
|
||||
</Header>
|
||||
|
||||
<Layout className="main-shell">
|
||||
{isMobileLayout && !collapsed ? (
|
||||
<button
|
||||
type="button"
|
||||
className="main-sider-backdrop"
|
||||
aria-label="关闭菜单"
|
||||
onClick={() => setCollapsed(true)}
|
||||
/>
|
||||
) : null}
|
||||
<Sider
|
||||
trigger={null}
|
||||
collapsible
|
||||
|
|
@ -414,6 +461,11 @@ export default function AppLayout() {
|
|||
selectedKeys={selectedMenuKeys}
|
||||
openKeys={collapsed ? [] : openKeys}
|
||||
onOpenChange={setOpenKeys}
|
||||
onClick={() => {
|
||||
if (isMobileLayout) {
|
||||
setCollapsed(true);
|
||||
}
|
||||
}}
|
||||
inlineCollapsed={collapsed}
|
||||
items={menuItems}
|
||||
className="main-menu"
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import "./index.less";
|
|||
|
||||
const { Text } = Typography;
|
||||
type TreePermission = SysPermission & { key: number; children?: TreePermission[] };
|
||||
type MenuIconComponent = React.ElementType<{ "aria-hidden"?: string | boolean }>;
|
||||
type MenuIconComponent = React.ElementType<{ "aria-hidden"?: boolean | "true" | "false" }>;
|
||||
|
||||
const legacyIconAliases: Record<string, string> = {
|
||||
dashboard: "DashboardOutlined",
|
||||
|
|
@ -611,7 +611,7 @@ export default function Permissions() {
|
|||
}
|
||||
>
|
||||
<Form form={form} layout="vertical" className="permission-form" onValuesChange={(changed) => changed.permType === "button" && form.setFieldsValue({ isVisible: 0 })}>
|
||||
<Row gutter={16}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col span={24}>
|
||||
<Form.Item label={t("permissions.permType")} name="permType" rules={[{ required: true }]}>
|
||||
<Select options={typeDict.map((item) => ({ value: item.itemValue, label: item.itemLabel }))} aria-label={t("permissions.permType")} />
|
||||
|
|
@ -627,19 +627,19 @@ export default function Permissions() {
|
|||
<Form.Item label={<Space><span>{t("permissions.permCode")}</span><Tooltip title={t("permissionsExt.codeHelp")}><InfoCircleOutlined className="text-gray-400" /></Tooltip></Space>} name="code" rules={[{ required: true, message: t("permissions.permCode") }]}>
|
||||
<Input placeholder={t("permissionsExt.codePlaceholder")} className="tabular-nums" />
|
||||
</Form.Item>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t("permissions.path")} name="path">
|
||||
<Input placeholder={t("permissionsExt.pathPlaceholder")} className="tabular-nums" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t("permissions.component")} name="component">
|
||||
<Input placeholder={t("permissionsExt.componentPlaceholder")} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col span={24}>
|
||||
<Form.Item label={t("permissions.icon")}>
|
||||
<Form.Item name="icon" noStyle>
|
||||
|
|
@ -712,20 +712,20 @@ export default function Permissions() {
|
|||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t("permissions.sort")} name="sortOrder" initialValue={0}>
|
||||
<InputNumber style={{ width: "100%" }} min={0} className="tabular-nums" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t("permissions.isVisible")} name="isVisible" initialValue={1}>
|
||||
<Select options={visibleDict.map((item) => ({ value: Number(item.itemValue), label: item.itemLabel }))} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t("common.status")} name="status" initialValue={1}>
|
||||
<Select options={statusDict.map((item) => ({ value: Number(item.itemValue), label: item.itemLabel }))} />
|
||||
</Form.Item>
|
||||
|
|
|
|||
|
|
@ -376,3 +376,77 @@
|
|||
border: 1px dashed #cccccc;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.roles-page-v2 > .page-container__body,
|
||||
.roles-page-v2 .section-card,
|
||||
.roles-page-v2 .section-card__content,
|
||||
.roles-data-panel,
|
||||
.roles-data-panel .data-list-panel__table-container,
|
||||
.roles-data-panel .data-list-panel__table-area,
|
||||
.roles-data-panel .app-page__table-wrap {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.roles-layout,
|
||||
.roles-layout__row {
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.roles-layout__row {
|
||||
row-gap: 12px;
|
||||
}
|
||||
|
||||
.roles-layout__side,
|
||||
.roles-layout__detail {
|
||||
height: auto;
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
.roles-side-card,
|
||||
.roles-detail-card {
|
||||
height: min(560px, calc(100vh - 180px));
|
||||
min-height: 360px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.roles-page-v2 {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.role-search-bar,
|
||||
.role-members-toolbar {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.role-detail-card > .ant-card-head {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.role-detail-card > .ant-card-head .ant-card-head-wrapper {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.role-detail-card > .ant-card-head .ant-card-extra,
|
||||
.role-detail-card > .ant-card-head .ant-card-extra .ant-btn,
|
||||
.role-search-bar .ant-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.roles-side-card,
|
||||
.roles-detail-card {
|
||||
height: min(520px, calc(100vh - 150px));
|
||||
min-height: 320px;
|
||||
}
|
||||
|
||||
.role-detail-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -442,10 +442,10 @@ export default function Roles() {
|
|||
>
|
||||
<div className="roles-layout">
|
||||
<Row gutter={8} className="roles-layout__row">
|
||||
<Col span={7} className="roles-layout__side">
|
||||
<Col xs={24} lg={7} className="roles-layout__side">
|
||||
<Card
|
||||
title={<Space><ApartmentOutlined /><span>{"角色列表"}</span></Space>}
|
||||
bordered={false}
|
||||
variant="borderless"
|
||||
className="app-page__panel-card roles-side-card"
|
||||
>
|
||||
<div className="role-search-panel">
|
||||
|
|
@ -493,11 +493,11 @@ export default function Roles() {
|
|||
<div className="role-item-actions">
|
||||
<Space size={4}>
|
||||
<Tooltip title="编辑">
|
||||
<Button type="text" size="small" icon={<EditOutlined />} onClick={(event) => openEditBasic(event, item)} />
|
||||
<Button type="text" size="small" icon={<EditOutlined />} onClick={(event) => openEditBasic(event, item)} aria-label="编辑角色" />
|
||||
</Tooltip>
|
||||
{item.roleCode !== "ADMIN" && (
|
||||
<Popconfirm title="确定删除该角色吗?" okText="确定" cancelText="取消" onConfirm={(event) => void handleRemove(event!, item.roleId)}>
|
||||
<Button type="text" size="small" danger icon={<DeleteOutlined />} onClick={(event) => event.stopPropagation()} />
|
||||
<Button type="text" size="small" danger icon={<DeleteOutlined />} onClick={(event) => event.stopPropagation()} aria-label="删除角色" title="删除角色" />
|
||||
</Popconfirm>
|
||||
)}
|
||||
</Space>
|
||||
|
|
@ -514,116 +514,133 @@ export default function Roles() {
|
|||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={17} className="roles-layout__detail">
|
||||
<Col xs={24} lg={17} className="roles-layout__detail">
|
||||
{selectedRole ? (
|
||||
<Card
|
||||
className="app-page__panel-card roles-detail-card"
|
||||
bordered={false}
|
||||
variant="borderless"
|
||||
title={<div className="role-detail-header"><div className="role-detail-icon"><SafetyCertificateOutlined /></div><div className="role-detail-heading"><div className="role-detail-title">{selectedRole.roleName}</div><Text type="secondary" className="role-detail-code">{selectedRole.roleCode}</Text></div></div>}
|
||||
extra={<Button type="primary" icon={<SaveOutlined />} loading={saving} onClick={handlePrimarySave} disabled={saveDisabled} style={{ borderRadius: "6px" }}>{saveLabel}</Button>}
|
||||
>
|
||||
<Tabs activeKey={activeTab} onChange={(key) => setActiveTab(key as RoleTabKey)} className="role-detail-tabs">
|
||||
<Tabs.TabPane tab={<Space><KeyOutlined />{"功能权限"}</Space>} key="permissions">
|
||||
<div className="role-detail-pane">
|
||||
<div className="permission-tree-wrapper">
|
||||
<Tree
|
||||
checkable
|
||||
selectable={false}
|
||||
checkStrictly={false}
|
||||
treeData={permissionTreeData}
|
||||
checkedKeys={selectedPermIds}
|
||||
onCheck={(keys, info) => {
|
||||
const checked = Array.isArray(keys) ? keys : keys.checked;
|
||||
const halfChecked = info.halfCheckedKeys || [];
|
||||
setSelectedPermIds(checked.map((key) => Number(key)));
|
||||
setHalfCheckedIds(halfChecked.map((key) => Number(key)));
|
||||
setPermissionsDirty(true);
|
||||
}}
|
||||
defaultExpandAll
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={<Space><ApartmentOutlined />{"数据权限"}</Space>} key="dataScope">
|
||||
<div className="role-detail-pane">
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Radio.Group
|
||||
value={dataScopeType}
|
||||
onChange={(event) => {
|
||||
setDataScopeType(event.target.value);
|
||||
setDataScopeDirty(true);
|
||||
}}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
>
|
||||
{DATA_SCOPE_OPTIONS.map((item) => (
|
||||
<Radio.Button key={item.value} value={item.value}>{item.label}</Radio.Button>
|
||||
))}
|
||||
</Radio.Group>
|
||||
</div>
|
||||
<div style={{ marginBottom: 16, color: "#64748b" }}>{getDataScopeDescription(dataScopeType)}</div>
|
||||
{dataScopeType === "CUSTOM" ? (
|
||||
<div className="permission-tree-wrapper">
|
||||
<Tree
|
||||
checkable
|
||||
selectable={false}
|
||||
treeData={scopeOrgTree}
|
||||
checkedKeys={scopeOrgIds}
|
||||
onCheck={(keys) => {
|
||||
const checked = Array.isArray(keys) ? keys : keys.checked;
|
||||
setScopeOrgIds(checked.map((key) => Number(key)));
|
||||
setDataScopeDirty(true);
|
||||
}}
|
||||
defaultExpandAll
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onChange={(key) => setActiveTab(key as RoleTabKey)}
|
||||
className="role-detail-tabs"
|
||||
items={[
|
||||
{
|
||||
key: "permissions",
|
||||
label: <Space><KeyOutlined />{"功能权限"}</Space>,
|
||||
children: (
|
||||
<div className="role-detail-pane">
|
||||
<div className="permission-tree-wrapper">
|
||||
<Tree
|
||||
checkable
|
||||
selectable={false}
|
||||
checkStrictly={false}
|
||||
treeData={permissionTreeData}
|
||||
checkedKeys={selectedPermIds}
|
||||
onCheck={(keys, info) => {
|
||||
const checked = Array.isArray(keys) ? keys : keys.checked;
|
||||
const halfChecked = info.halfCheckedKeys || [];
|
||||
setSelectedPermIds(checked.map((key) => Number(key)));
|
||||
setHalfCheckedIds(halfChecked.map((key) => Number(key)));
|
||||
setPermissionsDirty(true);
|
||||
}}
|
||||
defaultExpandAll
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "dataScope",
|
||||
label: <Space><ApartmentOutlined />{"数据权限"}</Space>,
|
||||
children: (
|
||||
<div className="role-detail-pane">
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Radio.Group
|
||||
value={dataScopeType}
|
||||
onChange={(event) => {
|
||||
setDataScopeType(event.target.value);
|
||||
setDataScopeDirty(true);
|
||||
}}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
>
|
||||
{DATA_SCOPE_OPTIONS.map((item) => (
|
||||
<Radio.Button key={item.value} value={item.value}>{item.label}</Radio.Button>
|
||||
))}
|
||||
</Radio.Group>
|
||||
</div>
|
||||
<div style={{ marginBottom: 16, color: "#64748b" }}>{getDataScopeDescription(dataScopeType)}</div>
|
||||
{dataScopeType === "CUSTOM" ? (
|
||||
<div className="permission-tree-wrapper">
|
||||
<Tree
|
||||
checkable
|
||||
selectable={false}
|
||||
treeData={scopeOrgTree}
|
||||
checkedKeys={scopeOrgIds}
|
||||
onCheck={(keys) => {
|
||||
const checked = Array.isArray(keys) ? keys : keys.checked;
|
||||
setScopeOrgIds(checked.map((key) => Number(key)));
|
||||
setDataScopeDirty(true);
|
||||
}}
|
||||
defaultExpandAll
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="当前范围不需要选择部门" />
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "users",
|
||||
label: <Space><TeamOutlined />{`成员管理 (${roleUsers.length})`}</Space>,
|
||||
children: (
|
||||
<div className="role-detail-pane">
|
||||
<div className="role-members-toolbar">
|
||||
<Title level={5} style={{ margin: 0 }}>{"已绑定用户"}</Title>
|
||||
<Button type="primary" ghost icon={<UserAddOutlined />} onClick={openUserModal} disabled={!can("sys:role:update")}>{"绑定用户"}</Button>
|
||||
</div>
|
||||
<Table
|
||||
rowKey="userId"
|
||||
size="small"
|
||||
loading={loadingUsers}
|
||||
dataSource={roleUsers}
|
||||
pagination={{ ...getStandardPagination(roleUsers.length, 1, 10, undefined, { size: "small", showSizeChanger: false }), current: undefined }}
|
||||
columns={[
|
||||
{
|
||||
title: "用户信息",
|
||||
render: (_: unknown, user: SysUser) => (
|
||||
<Space>
|
||||
<Avatar size="small" icon={<UserOutlined />} style={{ backgroundColor: "#f0f2f5", color: "#8c8c8c" }} />
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>{user.displayName}</div>
|
||||
<div style={{ fontSize: 11, color: "#bfbfbf" }}>@{user.username}</div>
|
||||
</div>
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
{ title: "手机号", dataIndex: "phone", className: "tabular-nums" },
|
||||
{ title: "状态", dataIndex: "status", width: 80, render: (status: number) => <Tag color={status === 1 ? "green" : "red"}>{status === 1 ? "启用" : "停用"}</Tag> },
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
width: 80,
|
||||
render: (_: unknown, user: SysUser) => (
|
||||
<Popconfirm title="确定解除该用户绑定吗?" okText="确定" cancelText="取消" onConfirm={() => void handleUnbindUser(user.userId)} disabled={!can("sys:role:update")}>
|
||||
<Button type="text" danger size="small" icon={<DeleteOutlined />} disabled={!can("sys:role:update")} aria-label="解除用户绑定" title="解除用户绑定" />
|
||||
</Popconfirm>
|
||||
)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="当前范围不需要选择部门" />
|
||||
)}
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={<Space><TeamOutlined />{`成员管理 (${roleUsers.length})`}</Space>} key="users">
|
||||
<div className="role-detail-pane">
|
||||
<div className="role-members-toolbar">
|
||||
<Title level={5} style={{ margin: 0 }}>{"已绑定用户"}</Title>
|
||||
<Button type="primary" ghost icon={<UserAddOutlined />} onClick={openUserModal} disabled={!can("sys:role:update")}>{"绑定用户"}</Button>
|
||||
</div>
|
||||
<Table
|
||||
rowKey="userId"
|
||||
size="small"
|
||||
loading={loadingUsers}
|
||||
dataSource={roleUsers}
|
||||
pagination={{ ...getStandardPagination(roleUsers.length, 1, 10, undefined, { size: "small", showSizeChanger: false }), current: undefined }}
|
||||
columns={[
|
||||
{
|
||||
title: "用户信息",
|
||||
render: (_: unknown, user: SysUser) => (
|
||||
<Space>
|
||||
<Avatar size="small" icon={<UserOutlined />} style={{ backgroundColor: "#f0f2f5", color: "#8c8c8c" }} />
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>{user.displayName}</div>
|
||||
<div style={{ fontSize: 11, color: "#bfbfbf" }}>@{user.username}</div>
|
||||
</div>
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
{ title: "手机号", dataIndex: "phone", className: "tabular-nums" },
|
||||
{ title: "状态", dataIndex: "status", width: 80, render: (status: number) => <Tag color={status === 1 ? "green" : "red"}>{status === 1 ? "启用" : "停用"}</Tag> },
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
width: 80,
|
||||
render: (_: unknown, user: SysUser) => (
|
||||
<Popconfirm title="确定解除该用户绑定吗?" okText="确定" cancelText="取消" onConfirm={() => void handleUnbindUser(user.userId)} disabled={!can("sys:role:update")}>
|
||||
<Button type="text" danger size="small" icon={<DeleteOutlined />} disabled={!can("sys:role:update")} />
|
||||
</Popconfirm>
|
||||
)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="app-page__empty-state"><Empty description="请选择左侧角色查看详情" /></div>
|
||||
|
|
@ -634,14 +651,14 @@ export default function Roles() {
|
|||
</DataListPanel>
|
||||
</SectionCard>
|
||||
|
||||
<Modal title="绑定用户到角色" open={userModalOpen} onCancel={() => setUserModalOpen(false)} onOk={() => void handleAddUsers()} okText="确定" cancelText="取消" width={650} destroyOnClose>
|
||||
<Modal title="绑定用户到角色" open={userModalOpen} onCancel={() => setUserModalOpen(false)} onOk={() => void handleAddUsers()} okText="确定" cancelText="取消" width={650} destroyOnHidden>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Input placeholder="搜索用户名或显示名称" prefix={<SearchOutlined />} value={userSearchText} onChange={(event) => setUserSearchText(event.target.value)} allowClear />
|
||||
</div>
|
||||
<Table rowKey="userId" size="small" dataSource={filteredModalUsers} pagination={{ ...getStandardPagination(filteredModalUsers.length, 1, 6, undefined, { size: "small", showSizeChanger: false }), current: undefined }} rowSelection={{ selectedRowKeys: selectedUserKeys, onChange: (keys) => setSelectedUserKeys(keys as number[]) }} columns={[{ title: "显示名称", dataIndex: "displayName" }, { title: "用户名", dataIndex: "username" }, { title: "手机号", dataIndex: "phone" }]} />
|
||||
</Modal>
|
||||
|
||||
<Drawer title={editing ? "编辑角色" : "新增角色"} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={420} destroyOnClose footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>{"取消"}</Button><Button type="primary" loading={saving} onClick={() => void submitBasic()}>{"保存"}</Button></div>}>
|
||||
<Drawer title={editing ? "编辑角色" : "新增角色"} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={420} destroyOnHidden footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>{"取消"}</Button><Button type="primary" loading={saving} onClick={() => void submitBasic()}>{"保存"}</Button></div>}>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item label="租户" name="tenantId" rules={[{ required: true }]} hidden={!isPlatformMode}>
|
||||
<Select options={tenants.map((tenant) => ({ label: tenant.tenantName, value: tenant.id }))} disabled={!!editing} />
|
||||
|
|
|
|||
|
|
@ -552,7 +552,7 @@ export default function Users() {
|
|||
</SectionCard>
|
||||
|
||||
<Drawer title={<div className="user-drawer-title"><UserOutlined aria-hidden="true"/><span>{editing ? t("users.drawerTitleEdit") : t("users.drawerTitleCreate")}</span>
|
||||
</div>} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={520} destroyOnClose
|
||||
</div>} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={520} destroyOnHidden
|
||||
footer={<div className="app-page__drawer-footer"><Button
|
||||
onClick={() => setDrawerOpen(false)}>{t("common.cancel")}</Button><Button type="primary"
|
||||
loading={saving}
|
||||
|
|
@ -560,8 +560,8 @@ export default function Users() {
|
|||
</div>}>
|
||||
<Form form={form} layout="vertical" className="user-form">
|
||||
<Title level={5} style={{ marginBottom: 16 }}>{t("usersExt.basicInfo")}</Title>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}><Form.Item label={t("users.username")} name="username" rules={[{
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}><Form.Item label={t("users.username")} name="username" rules={[{
|
||||
required: true,
|
||||
message: t("users.username")
|
||||
}, {
|
||||
|
|
@ -571,17 +571,17 @@ export default function Users() {
|
|||
extra={t("usersExt.usernameFormatTip", {defaultValue: "登录名只能输入数字、小写英文、@ 和 _"})}><Input
|
||||
placeholder={t("usersExt.usernamePlaceholder", {defaultValue: "仅支持 a-z、0-9、@、_"})} disabled={!!editing}
|
||||
className="tabular-nums"/></Form.Item></Col>
|
||||
<Col span={12}><Form.Item label={t("users.displayName")} name="displayName"
|
||||
<Col xs={24} md={12}><Form.Item label={t("users.displayName")} name="displayName"
|
||||
rules={[{required: true, message: t("users.displayName")}]}><Input
|
||||
placeholder={t("users.displayName")}/></Form.Item></Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}><Form.Item label={t("users.email")} name="email"
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}><Form.Item label={t("users.email")} name="email"
|
||||
rules={[{required: true, message: "请输入邮箱地址"}, {
|
||||
type: "email",
|
||||
message: "请输入正确的邮箱格式"
|
||||
}]}><Input placeholder={t("usersExt.emailPlaceholder")} className="tabular-nums"/></Form.Item></Col>
|
||||
<Col span={12}><Form.Item label={t("users.phone")} name="phone"><Input placeholder={t("users.phone")}
|
||||
<Col xs={24} md={12}><Form.Item label={t("users.phone")} name="phone"><Input placeholder={t("users.phone")}
|
||||
className="tabular-nums"/></Form.Item></Col>
|
||||
</Row>
|
||||
<Form.Item label={t("profile.avatarUrl")} name="avatarUrl"><Input
|
||||
|
|
@ -610,13 +610,13 @@ export default function Users() {
|
|||
<>
|
||||
{fields.map(({ key, name, ...restField }) => (
|
||||
<Card key={key} size="small" className="mb-3" styles={{ body: { padding: "12px" } }} title={t("usersExt.membershipTitle", { index: name + 1 })} extra={fields.length > 1 && <Button type="text" danger icon={<MinusCircleOutlined />} onClick={() => remove(name)} />}>
|
||||
<Row gutter={12}>
|
||||
<Col span={12}>
|
||||
<Row gutter={12} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item {...restField} label={t("users.tenant")} name={[name, "tenantId"]} rules={[{ required: true, message: t("usersExt.membershipRequired") }]}>
|
||||
<Select options={tenants.map((tenant) => ({ label: tenant.tenantName, value: tenant.id }))} placeholder={t("usersExt.selectTenant")} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Col xs={24} md={12}>
|
||||
<MembershipOrgSelect fieldProps={{ ...restField }} name={name} tenantId={form.getFieldValue(["memberships", name, "tenantId"])} />
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
@ -683,8 +683,8 @@ export default function Users() {
|
|||
<Input.Password placeholder={t("usersExt.confirmPasswordPlaceholder")} autoComplete="new-password" />
|
||||
</Form.Item>
|
||||
) : null}
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item
|
||||
label={t("common.status")}
|
||||
name="status"
|
||||
|
|
@ -697,7 +697,7 @@ export default function Users() {
|
|||
</Form.Item>
|
||||
</Col>
|
||||
{isPlatformMode &&
|
||||
<Col span={12}><Form.Item label={t("users.platformAdmin")} name="isPlatformAdmin" valuePropName="checked"><Switch/></Form.Item></Col>}
|
||||
<Col xs={24} md={12}><Form.Item label={t("users.platformAdmin")} name="isPlatformAdmin" valuePropName="checked"><Switch/></Form.Item></Col>}
|
||||
</Row>
|
||||
</Form>
|
||||
</Drawer>
|
||||
|
|
@ -712,7 +712,7 @@ export default function Users() {
|
|||
}}
|
||||
onOk={() => void submitResetPassword()}
|
||||
confirmLoading={resetPasswordLoading}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
>
|
||||
<Form form={resetPasswordForm} layout="vertical">
|
||||
<Form.Item
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@
|
|||
margin: 0 8px 8px;
|
||||
}
|
||||
|
||||
.role-permission-layout > .ant-col {
|
||||
.role-permission-layout > .ant-col,
|
||||
.role-permission-layout__pane {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
|
@ -34,3 +35,42 @@
|
|||
.role-permission-layout .full-height-card .ant-card-body {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.role-permission-page > .page-container__body,
|
||||
.role-permission-page .section-card,
|
||||
.role-permission-page .section-card__content {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.role-permission-layout {
|
||||
overflow: visible;
|
||||
row-gap: 12px;
|
||||
}
|
||||
|
||||
.role-permission-layout > .ant-col,
|
||||
.role-permission-layout__pane {
|
||||
height: auto;
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
.role-permission-layout .full-height-card {
|
||||
height: min(430px, calc(100vh - 170px));
|
||||
min-height: 360px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.role-permission-page {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.role-permission-toolbar {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.role-permission-layout .full-height-card {
|
||||
height: min(390px, calc(100vh - 155px));
|
||||
min-height: 320px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ export default function RolePermissionBinding() {
|
|||
<div className="app-page__content-toolbar-filters" />
|
||||
</div>
|
||||
<Row gutter={16} className="role-permission-layout">
|
||||
<Col xs={24} lg={10} style={{ height: "100%" }}>
|
||||
<Col xs={24} lg={10} className="role-permission-layout__pane">
|
||||
<Card title={<Space><SafetyCertificateOutlined aria-hidden="true" /><span>{t("rolePerm.roleList")}</span></Space>} className="app-page__panel-card full-height-card">
|
||||
<div className="mb-4">
|
||||
<Input
|
||||
|
|
@ -247,7 +247,7 @@ export default function RolePermissionBinding() {
|
|||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} lg={14} style={{ height: "100%" }}>
|
||||
<Col xs={24} lg={14} className="role-permission-layout__pane">
|
||||
<Card
|
||||
title={<Space><KeyOutlined aria-hidden="true" /><span>{t("rolePerm.permConfig")}</span></Space>}
|
||||
className="app-page__panel-card full-height-card"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@
|
|||
margin: 0 8px 8px;
|
||||
}
|
||||
|
||||
.user-role-layout > .ant-col {
|
||||
.user-role-layout > .ant-col,
|
||||
.user-role-layout__pane {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
|
@ -34,3 +35,42 @@
|
|||
.user-role-layout .full-height-card .ant-card-body {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.user-role-page > .page-container__body,
|
||||
.user-role-page .section-card,
|
||||
.user-role-page .section-card__content {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.user-role-layout {
|
||||
overflow: visible;
|
||||
row-gap: 12px;
|
||||
}
|
||||
|
||||
.user-role-layout > .ant-col,
|
||||
.user-role-layout__pane {
|
||||
height: auto;
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
.user-role-layout .full-height-card {
|
||||
height: min(430px, calc(100vh - 170px));
|
||||
min-height: 360px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.user-role-page {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.user-role-toolbar {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.user-role-layout .full-height-card {
|
||||
height: min(390px, calc(100vh - 155px));
|
||||
min-height: 320px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ export default function UserRoleBinding() {
|
|||
<div className="app-page__content-toolbar-filters" />
|
||||
</div>
|
||||
<Row gutter={16} className="user-role-layout">
|
||||
<Col xs={24} lg={12} style={{ height: "100%" }}>
|
||||
<Col xs={24} lg={12} className="user-role-layout__pane">
|
||||
<Card title={<Space><UserOutlined aria-hidden="true" /><span>{t("userRole.userList")}</span></Space>} className="app-page__panel-card full-height-card">
|
||||
<div className="mb-4">
|
||||
<Input
|
||||
|
|
@ -167,7 +167,7 @@ export default function UserRoleBinding() {
|
|||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} lg={12} style={{ height: "100%" }}>
|
||||
<Col xs={24} lg={12} className="user-role-layout__pane">
|
||||
<Card
|
||||
title={<Space><TeamOutlined aria-hidden="true" /><span>{t("userRole.grantRoles")}</span></Space>}
|
||||
className="app-page__panel-card full-height-card"
|
||||
|
|
@ -178,7 +178,7 @@ export default function UserRoleBinding() {
|
|||
<Checkbox.Group style={{ width: "100%" }} value={checkedRoleIds} onChange={(values) => setCheckedRoleIds(values as number[])} disabled={loadingRoles}>
|
||||
<Row gutter={[16, 16]}>
|
||||
{roles.map((role) => (
|
||||
<Col key={role.roleId} span={12}>
|
||||
<Col key={role.roleId} xs={24} md={12}>
|
||||
<Checkbox value={role.roleId} className="w-full">
|
||||
<Space direction="vertical" size={0}>
|
||||
<span style={{ fontWeight: 500 }}>{role.roleName}</span>
|
||||
|
|
|
|||
|
|
@ -623,8 +623,8 @@ const AiModels: React.FC = () => {
|
|||
</Tag>
|
||||
</Form.Item>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item
|
||||
name="modelName"
|
||||
label="显示名称"
|
||||
|
|
@ -635,7 +635,7 @@ const AiModels: React.FC = () => {
|
|||
}}/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item
|
||||
name="provider"
|
||||
label="提供商"
|
||||
|
|
@ -652,8 +652,8 @@ const AiModels: React.FC = () => {
|
|||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item name="sortOrder" label="排序值">
|
||||
<InputNumber min={0} style={{ width: "100%" }} />
|
||||
</Form.Item>
|
||||
|
|
@ -724,8 +724,8 @@ const AiModels: React.FC = () => {
|
|||
</Form.Item>
|
||||
|
||||
{activeType === "ASR" && isLocalProvider && (
|
||||
<Row gutter={16} hidden>
|
||||
<Col span={12}>
|
||||
<Row gutter={16} hidden className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item name="svThreshold" label="声纹阈值">
|
||||
<InputNumber min={0} max={1} step={0.01} style={{ width: "100%" }} />
|
||||
</Form.Item>
|
||||
|
|
@ -734,13 +734,13 @@ const AiModels: React.FC = () => {
|
|||
)}
|
||||
|
||||
{activeType === "ASR" && isTencentProvider && (
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item name="tencentAppId" label="App ID" rules={[{required: true, message: "请输入 App ID"}]}>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item name="tencentSecretId" label="Secret ID"
|
||||
rules={[{required: true, message: "请输入 Secret ID"}]}>
|
||||
<Input/>
|
||||
|
|
@ -752,13 +752,13 @@ const AiModels: React.FC = () => {
|
|||
<Input.Password/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item name="tencentOfflineModelCode" label="离线识别模型"
|
||||
rules={[{required: true, message: "请输入离线识别模型"}]}>
|
||||
<Input placeholder="例如:16k_zh"/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item name="tencentRealtimeModelCode" label="实时识别模型"
|
||||
rules={[{required: true, message: "请输入实时识别模型"}]}>
|
||||
<Input placeholder="例如:16k_zh_realtime"/>
|
||||
|
|
@ -772,13 +772,13 @@ const AiModels: React.FC = () => {
|
|||
<Form.Item name="apiPath" label="API 路径" initialValue="/v1/chat/completions">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item name="temperature" label="Temperature">
|
||||
<InputNumber min={0} max={2} step={0.1} style={{ width: "100%" }} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item name="topP" label="Top P">
|
||||
<InputNumber min={0} max={1} step={0.1} style={{ width: "100%" }} />
|
||||
</Form.Item>
|
||||
|
|
@ -787,8 +787,8 @@ const AiModels: React.FC = () => {
|
|||
</>
|
||||
)}
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={8}>
|
||||
<Form.Item name="isDefaultChecked" label="设为默认" valuePropName="checked">
|
||||
<Switch
|
||||
checkedChildren="是"
|
||||
|
|
@ -801,7 +801,7 @@ const AiModels: React.FC = () => {
|
|||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Col xs={24} md={8}>
|
||||
<Form.Item name="statusChecked" label="状态" valuePropName="checked">
|
||||
<Switch checkedChildren="启用" unCheckedChildren="禁用" disabled={Boolean(isDefaultChecked)} />
|
||||
</Form.Item>
|
||||
|
|
|
|||
|
|
@ -16,3 +16,16 @@
|
|||
gap: 12px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.client-management-page > .page-container__body,
|
||||
.client-management-page .section-card,
|
||||
.client-management-page .section-card__content {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.client-management-page__content > .data-list-panel {
|
||||
flex: 0 0 min(560px, calc(100vh - 140px));
|
||||
min-height: 430px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -403,10 +403,10 @@ export default function ClientManagement() {
|
|||
width: 150,
|
||||
render: (_, record) => (
|
||||
<Space size={4}>
|
||||
<Button type="text" icon={<EditOutlined />} onClick={() => openEdit(record)} />
|
||||
<Button type="text" icon={<DownloadOutlined />} href={record.downloadUrl} target="_blank" />
|
||||
<Button type="text" icon={<EditOutlined />} onClick={() => openEdit(record)} aria-label="编辑客户端版本" title="编辑客户端版本" />
|
||||
<Button type="text" icon={<DownloadOutlined />} href={record.downloadUrl} target="_blank" aria-label="下载安装包" title="下载安装包" />
|
||||
<Popconfirm title="确认删除该版本吗?" onConfirm={() => void handleDelete(record)}>
|
||||
<Button type="text" danger icon={<DeleteOutlined />} />
|
||||
<Button type="text" danger icon={<DeleteOutlined />} aria-label="删除客户端版本" title="删除客户端版本" />
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
|
|
@ -473,7 +473,7 @@ export default function ClientManagement() {
|
|||
columns={columns}
|
||||
dataSource={pagedRecords}
|
||||
loading={loading || groupLoading || platformLoading}
|
||||
scroll={{ x: 1100, y: "100%" }}
|
||||
scroll={{ x: "max-content", y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
</DataListPanel>
|
||||
|
|
@ -481,8 +481,8 @@ export default function ClientManagement() {
|
|||
|
||||
<Drawer title={editing ? "编辑客户端版本" : "新增客户端版本"} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={680} destroyOnHidden forceRender footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>取消</Button><Button type="primary" icon={<UploadOutlined />} loading={saving} onClick={() => void handleSubmit()}>保存</Button></div>}>
|
||||
<Form form={form} layout="vertical">
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item name="platformCode" label="发布平台" rules={[{ required: true, message: "请选择发布平台" }]}>
|
||||
<Select placeholder="选择平台" disabled={!!editing || platformGroups.length === 0}>
|
||||
{platformGroups.map((group) => (
|
||||
|
|
@ -495,7 +495,7 @@ export default function ClientManagement() {
|
|||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label="安装包上传">
|
||||
<Upload showUploadList={false} beforeUpload={(file) => { void handleUpload(file as File); return Upload.LIST_IGNORE; }}>
|
||||
<Button icon={<UploadOutlined />} loading={uploading} style={{ width: "100%" }}>上传安装包</Button>
|
||||
|
|
@ -504,18 +504,18 @@ export default function ClientManagement() {
|
|||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}><Form.Item name="version" label="版本号" rules={[{ required: true, message: "请输入版本号" }]}><Input placeholder="如 1.0.0" /></Form.Item></Col>
|
||||
<Col span={12}><Form.Item name="versionCode" label="版本码"><InputNumber min={0} style={{ width: "100%" }} placeholder="如 1000" /></Form.Item></Col>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}><Form.Item name="version" label="版本号" rules={[{ required: true, message: "请输入版本号" }]}><Input placeholder="如 1.0.0" /></Form.Item></Col>
|
||||
<Col xs={24} md={12}><Form.Item name="versionCode" label="版本码"><InputNumber min={0} style={{ width: "100%" }} placeholder="如 1000" /></Form.Item></Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item name="downloadUrl" label="下载链接" rules={[{ required: true, message: "请输入下载链接" }]}>
|
||||
<Input placeholder="https://..." />
|
||||
</Form.Item>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}><Form.Item name="fileSize" label="文件大小(Bytes)"><InputNumber min={0} style={{ width: "100%" }} /></Form.Item></Col>
|
||||
<Col span={12}><Form.Item name="minSystemVersion" label="最低系统要求"><Input placeholder="如 Android 8.0 / Windows 10" /></Form.Item></Col>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}><Form.Item name="fileSize" label="文件大小(Bytes)"><InputNumber min={0} style={{ width: "100%" }} /></Form.Item></Col>
|
||||
<Col xs={24} md={12}><Form.Item name="minSystemVersion" label="最低系统要求"><Input placeholder="如 Android 8.0 / Windows 10" /></Form.Item></Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item name="releaseNotes" label="更新说明"><TextArea rows={4} placeholder="请输入版本更新内容" /></Form.Item>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,19 @@
|
|||
padding: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.external-app-page > .page-container__body,
|
||||
.external-app-page .section-card,
|
||||
.external-app-page .section-card__content {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.external-app-page__content > .data-list-panel {
|
||||
flex: 0 0 min(520px, calc(100vh - 140px));
|
||||
min-height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
.external-app-icon-form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -306,10 +306,10 @@ export default function ExternalAppManagement() {
|
|||
const link = record.appType === "native" ? info.apkUrl : info.webUrl;
|
||||
return (
|
||||
<Space size={4}>
|
||||
<Button type="text" icon={<LinkOutlined />} href={link} target="_blank" disabled={!link} />
|
||||
<Button type="text" icon={<EditOutlined />} onClick={() => openEdit(record)} />
|
||||
<Button type="text" icon={<LinkOutlined />} href={link} target="_blank" disabled={!link} aria-label="打开应用入口" title="打开应用入口" />
|
||||
<Button type="text" icon={<EditOutlined />} onClick={() => openEdit(record)} aria-label="编辑外部应用" title="编辑外部应用" />
|
||||
<Popconfirm title="确认删除该应用吗?" onConfirm={() => void handleDelete(record)}>
|
||||
<Button type="text" danger icon={<DeleteOutlined />} />
|
||||
<Button type="text" danger icon={<DeleteOutlined />} aria-label="删除外部应用" title="删除外部应用" />
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
);
|
||||
|
|
@ -366,9 +366,9 @@ export default function ExternalAppManagement() {
|
|||
|
||||
<Drawer title={editing ? "编辑外部应用" : "新增外部应用"} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={700} destroyOnHidden forceRender footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>取消</Button><Button type="primary" icon={<SaveOutlined />} loading={saving} onClick={() => void handleSubmit()}>保存</Button></div>}>
|
||||
<Form form={form} layout="vertical">
|
||||
<Row gutter={16}>
|
||||
<Col span={16}><Form.Item name="appName" label="应用名称" rules={[{ required: true, message: "请输入应用名称" }]}><Input placeholder="请输入应用名称" /></Form.Item></Col>
|
||||
<Col span={8}><Form.Item name="appType" label="应用类型" rules={[{ required: true, message: "请选择应用类型" }]}><Select options={[{ label: "原生应用", value: "native" }, { label: "Web 应用", value: "web" }]} /></Form.Item></Col>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={16}><Form.Item name="appName" label="应用名称" rules={[{ required: true, message: "请输入应用名称" }]}><Input placeholder="请输入应用名称" /></Form.Item></Col>
|
||||
<Col xs={24} md={8}><Form.Item name="appType" label="应用类型" rules={[{ required: true, message: "请选择应用类型" }]}><Select options={[{ label: "原生应用", value: "native" }, { label: "Web 应用", value: "web" }]} /></Form.Item></Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item name="iconUrl" hidden>
|
||||
|
|
@ -410,26 +410,26 @@ export default function ExternalAppManagement() {
|
|||
<Button icon={<UploadOutlined />} loading={uploadingApk}>上传并回填 APK 信息</Button>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}><Form.Item name={["appInfo", "versionName"]} label="版本号"><Input placeholder="如 1.0.0" /></Form.Item></Col>
|
||||
<Col span={12}><Form.Item name={["appInfo", "packageName"]} label="包名"><Input placeholder="com.example.app" /></Form.Item></Col>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}><Form.Item name={["appInfo", "versionName"]} label="版本号"><Input placeholder="如 1.0.0" /></Form.Item></Col>
|
||||
<Col xs={24} md={12}><Form.Item name={["appInfo", "packageName"]} label="包名"><Input placeholder="com.example.app" /></Form.Item></Col>
|
||||
</Row>
|
||||
<Form.Item name={["appInfo", "apkUrl"]} label="APK 地址" rules={[{ required: true, message: "请上传或填写 APK 地址" }]}><Input prefix={<LinkOutlined />} placeholder="https://..." /></Form.Item>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}><Form.Item name={["appInfo", "versionName"]} label="版本号"><Input placeholder="可选" /></Form.Item></Col>
|
||||
<Col span={12}><Form.Item name={["appInfo", "webUrl"]} label="Web 地址" rules={[{ required: true, message: "请输入 Web 地址" }]}><Input prefix={<GlobalOutlined />} placeholder="https://..." /></Form.Item></Col>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}><Form.Item name={["appInfo", "versionName"]} label="版本号"><Input placeholder="可选" /></Form.Item></Col>
|
||||
<Col xs={24} md={12}><Form.Item name={["appInfo", "webUrl"]} label="Web 地址" rules={[{ required: true, message: "请输入 Web 地址" }]}><Input prefix={<GlobalOutlined />} placeholder="https://..." /></Form.Item></Col>
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="description" label="应用描述"><TextArea rows={3} placeholder="请输入应用用途说明" /></Form.Item>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}><Form.Item name="sortOrder" label="排序权重"><InputNumber min={0} style={{ width: "100%" }} /></Form.Item></Col>
|
||||
<Col span={12}><Form.Item name="remark" label="备注"><Input placeholder="选填" /></Form.Item></Col>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}><Form.Item name="sortOrder" label="排序权重"><InputNumber min={0} style={{ width: "100%" }} /></Form.Item></Col>
|
||||
<Col xs={24} md={12}><Form.Item name="remark" label="备注"><Input placeholder="选填" /></Form.Item></Col>
|
||||
</Row>
|
||||
<Form.Item name="statusEnabled" label="启用状态" valuePropName="checked"><Switch /></Form.Item>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -320,16 +320,30 @@
|
|||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.hotwords-page > .page-container__body,
|
||||
.hotwords-page .section-card,
|
||||
.hotwords-page .section-card__content {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.hotwords-layout {
|
||||
flex: none;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.hotwords-group-panel.ant-card {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
height: min(430px, calc(100vh - 170px));
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
.hotwords-list-panel {
|
||||
flex: none;
|
||||
height: min(560px, calc(100vh - 150px));
|
||||
min-height: 430px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
|
@ -340,4 +354,14 @@
|
|||
.hotwords-list-panel .data-list-panel__right-actions .ant-select {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.hotwords-group-panel.ant-card {
|
||||
height: min(390px, calc(100vh - 155px));
|
||||
min-height: 320px;
|
||||
}
|
||||
|
||||
.hotwords-list-panel {
|
||||
height: min(520px, calc(100vh - 135px));
|
||||
min-height: 380px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,3 +16,16 @@
|
|||
gap: 12px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.license-management-page > .page-container__body,
|
||||
.license-management-page .section-card,
|
||||
.license-management-page .section-card__content {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.license-management-page__content > .data-list-panel {
|
||||
flex: 0 0 min(520px, calc(100vh - 140px));
|
||||
min-height: 400px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ export default function LicenseManagement() {
|
|||
dataSource={pagedRecords}
|
||||
loading={loading}
|
||||
pagination={false}
|
||||
scroll={{ x: 1000, y: "100%" }}
|
||||
scroll={{ x: "max-content", y: "100%" }}
|
||||
/>
|
||||
</DataListPanel>
|
||||
</SectionCard>
|
||||
|
|
|
|||
|
|
@ -599,8 +599,8 @@ const MeetingProgressDisplay: React.FC<{
|
|||
<Text type="secondary">分析进行中,请稍候,你可以先处理其他工作。</Text>
|
||||
</div>
|
||||
<Divider style={{ margin: '32px 0' }} />
|
||||
<Row gutter={24}>
|
||||
<Col span={8}>
|
||||
<Row gutter={[24, 16]}>
|
||||
<Col xs={24} md={8}>
|
||||
<Space direction="vertical" size={0}>
|
||||
<Text type="secondary">当前进度</Text>
|
||||
<Title level={4} style={{ margin: 0 }}>
|
||||
|
|
@ -608,7 +608,7 @@ const MeetingProgressDisplay: React.FC<{
|
|||
</Title>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Col xs={24} md={8}>
|
||||
<Space direction="vertical" size={0}>
|
||||
<Text type="secondary">预计剩余</Text>
|
||||
<Title level={4} style={{ margin: 0 }}>
|
||||
|
|
@ -616,7 +616,7 @@ const MeetingProgressDisplay: React.FC<{
|
|||
</Title>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Col xs={24} md={8}>
|
||||
<Space direction="vertical" size={0}>
|
||||
<Text type="secondary">任务状态</Text>
|
||||
<Title level={4} style={{ margin: 0, color: isError ? '#ff4d4f' : '#52c41a' }}>
|
||||
|
|
@ -942,8 +942,8 @@ const UnifiedMeetingProgressDisplay: React.FC<{
|
|||
{renderStageTimeline()}
|
||||
|
||||
<Divider style={{ margin: '32px 0 24px' }} />
|
||||
<Row gutter={24}>
|
||||
<Col span={8}>
|
||||
<Row gutter={[24, 16]}>
|
||||
<Col xs={24} md={8}>
|
||||
<Space direction="vertical" size={0}>
|
||||
<Text type="secondary">当前阶段</Text>
|
||||
<Title level={4} style={{ margin: 0 }}>
|
||||
|
|
@ -951,7 +951,7 @@ const UnifiedMeetingProgressDisplay: React.FC<{
|
|||
</Title>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Col xs={24} md={8}>
|
||||
<Space direction="vertical" size={0}>
|
||||
<Text type="secondary">任务状态</Text>
|
||||
<Title level={4} style={{ margin: 0, color: isError ? '#ff4d4f' : '#52c41a' }}>
|
||||
|
|
@ -959,7 +959,7 @@ const UnifiedMeetingProgressDisplay: React.FC<{
|
|||
</Title>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Col xs={24} md={8}>
|
||||
<Space direction="vertical" size={0}>
|
||||
<Text type="secondary">可查看内容</Text>
|
||||
<Title level={4} style={{ margin: 0, color: '#4f46e5' }}>
|
||||
|
|
|
|||
|
|
@ -456,7 +456,7 @@ export default function MeetingPointsManagement() {
|
|||
columns={overviewColumns}
|
||||
dataSource={overviewRows}
|
||||
loading={false}
|
||||
scroll={{ x: 900, y: "100%" }}
|
||||
scroll={{ x: "max-content", y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
) : activeTabKey === "personal" ? (
|
||||
|
|
@ -466,7 +466,7 @@ export default function MeetingPointsManagement() {
|
|||
columns={personalAccountColumns}
|
||||
dataSource={pagedPersonalAccounts}
|
||||
loading={false}
|
||||
scroll={{ x: 900, y: "100%" }}
|
||||
scroll={{ x: "max-content", y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
) : (
|
||||
|
|
@ -476,7 +476,7 @@ export default function MeetingPointsManagement() {
|
|||
columns={ledgerColumns}
|
||||
dataSource={records}
|
||||
loading={loading}
|
||||
scroll={{ x: 1100, y: "100%" }}
|
||||
scroll={{ x: "max-content", y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -477,7 +477,14 @@
|
|||
}
|
||||
|
||||
.meetings-table__title-link {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #1677ff;
|
||||
font: inherit;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.meetings-table__source-tag.ant-tag {
|
||||
|
|
|
|||
|
|
@ -700,7 +700,7 @@ const Meetings: React.FC = () => {
|
|||
title: "会议标题",
|
||||
dataIndex: "title",
|
||||
key: "title",
|
||||
render: (text: string, record: MeetingVO) => <a className="meetings-table__title-link" onClick={() => handleOpenMeeting(record)}>{text}</a>,
|
||||
render: (text: string, record: MeetingVO) => <button type="button" className="meetings-table__title-link" onClick={() => handleOpenMeeting(record)}>{text}</button>,
|
||||
},
|
||||
{
|
||||
title: "状态",
|
||||
|
|
|
|||
|
|
@ -368,15 +368,15 @@ const PromptTemplates: React.FC = () => {
|
|||
return (
|
||||
<Space size={2} onClick={(e) => e.stopPropagation()}>
|
||||
<Tooltip title="查看">
|
||||
<Button type="text" size="small" icon={<EyeOutlined />} onClick={() => showDetail(item)} />
|
||||
<Button type="text" size="small" icon={<EyeOutlined />} onClick={() => showDetail(item)} aria-label="查看模板" />
|
||||
</Tooltip>
|
||||
{canEdit && (
|
||||
<Tooltip title="编辑">
|
||||
<Button type="text" size="small" icon={<EditOutlined />} onClick={() => handleOpenDrawer(item)} />
|
||||
<Button type="text" size="small" icon={<EditOutlined />} onClick={() => handleOpenDrawer(item)} aria-label="编辑模板" />
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title="以此创建">
|
||||
<Button type="text" size="small" icon={<CopyOutlined />} onClick={() => handleOpenDrawer(item, true)} />
|
||||
<Button type="text" size="small" icon={<CopyOutlined />} onClick={() => handleOpenDrawer(item, true)} aria-label="以此创建模板" />
|
||||
</Tooltip>
|
||||
{canEdit && (
|
||||
<Popconfirm
|
||||
|
|
@ -386,7 +386,7 @@ const PromptTemplates: React.FC = () => {
|
|||
cancelText={t('common.cancel')}
|
||||
>
|
||||
<Tooltip title="删除">
|
||||
<Button type="text" size="small" danger icon={<DeleteOutlined />} />
|
||||
<Button type="text" size="small" danger icon={<DeleteOutlined />} aria-label="删除模板" />
|
||||
</Tooltip>
|
||||
</Popconfirm>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export default function PublicDeviceMeetingCreate() {
|
|||
</div>
|
||||
</Space>
|
||||
|
||||
<Card bordered={false} className="public-device-create-notice">
|
||||
<Card variant="borderless" className="public-device-create-notice">
|
||||
<Space direction="vertical" size={12}>
|
||||
<Text>
|
||||
<CheckCircleOutlined className="public-device-create-check" />
|
||||
|
|
|
|||
|
|
@ -150,10 +150,15 @@
|
|||
.screen-saver-crop-modal .ant-modal-content {
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
max-height: calc(100vh - 48px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.screen-saver-crop-modal .ant-modal-body {
|
||||
padding: 0;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.screen-saver-crop-modal__layout {
|
||||
|
|
@ -287,10 +292,13 @@
|
|||
@media (max-width: 992px) {
|
||||
.screen-saver-crop-modal__layout {
|
||||
grid-template-columns: 1fr;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.screen-saver-crop-modal__stage {
|
||||
padding: 24px;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.screen-saver-crop-modal__sidebar {
|
||||
|
|
@ -311,4 +319,44 @@
|
|||
.screen-saver-page .screen-saver-table-thumb {
|
||||
width: 88px;
|
||||
}
|
||||
|
||||
.screen-saver-crop-modal {
|
||||
max-width: calc(100vw - 24px);
|
||||
}
|
||||
|
||||
.screen-saver-crop-modal__stage {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.screen-saver-crop-modal__viewport {
|
||||
width: min(100%, 360px);
|
||||
}
|
||||
|
||||
.screen-saver-crop-modal__sidebar {
|
||||
padding: 16px 20px 20px;
|
||||
}
|
||||
|
||||
.screen-saver-crop-modal__footer {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 680px) {
|
||||
.screen-saver-crop-modal .ant-modal-content {
|
||||
max-height: calc(100vh - 24px);
|
||||
}
|
||||
|
||||
.screen-saver-crop-modal__layout {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.screen-saver-crop-modal__stage {
|
||||
gap: 14px;
|
||||
padding-top: 18px;
|
||||
padding-bottom: 18px;
|
||||
}
|
||||
|
||||
.screen-saver-crop-modal__viewport {
|
||||
width: min(100%, 520px);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -707,9 +707,9 @@ export default function ScreenSaverManagement() {
|
|||
}
|
||||
return (
|
||||
<Space size={4}>
|
||||
<Button type="text" icon={<EditOutlined />} onClick={() => openEdit(record)} />
|
||||
<Button type="text" icon={<EditOutlined />} onClick={() => openEdit(record)} aria-label="编辑屏保" title="编辑屏保" />
|
||||
<Popconfirm title="确认删除该屏保吗?" onConfirm={() => void handleDelete(record)}>
|
||||
<Button type="text" danger icon={<DeleteOutlined />} />
|
||||
<Button type="text" danger icon={<DeleteOutlined />} aria-label="删除屏保" title="删除屏保" />
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
);
|
||||
|
|
@ -798,7 +798,7 @@ export default function ScreenSaverManagement() {
|
|||
locale={{
|
||||
emptyText: <Empty description="暂无屏保素材" />,
|
||||
}}
|
||||
scroll={{ x: 1100, y: "100%" }}
|
||||
scroll={{ x: "max-content", y: "100%" }}
|
||||
/>
|
||||
</DataListPanel>
|
||||
</SectionCard>
|
||||
|
|
|
|||
|
|
@ -708,7 +708,7 @@ const SpeakerReg: React.FC = () => {
|
|||
columns={columns}
|
||||
dataSource={filteredSpeakers}
|
||||
loading={listLoading}
|
||||
scroll={{ x: 800, y: "100%" }}
|
||||
scroll={{ x: "max-content", y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
|
||||
|
|
@ -806,4 +806,4 @@ const SpeakerReg: React.FC = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default SpeakerReg;
|
||||
export default SpeakerReg;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,28 @@
|
|||
background: transparent;
|
||||
}
|
||||
|
||||
.tenant-meeting-points__toolbar {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tenant-meeting-points__filters {
|
||||
justify-content: flex-end;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.tenant-meeting-points__filter {
|
||||
width: var(--app-control-min-width, 180px);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tenant-meeting-points__filter--wide {
|
||||
width: var(--app-control-wide-width, 220px);
|
||||
}
|
||||
|
||||
.tenant-meeting-points__list-panel .ant-table-cell {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tenant-meeting-points__tenant-section {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
|
@ -47,6 +69,10 @@
|
|||
gap: 12px;
|
||||
}
|
||||
|
||||
.tenant-meeting-points__tenant-identity .ant-space {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tenant-meeting-points__tenant-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -69,6 +95,7 @@
|
|||
|
||||
.tenant-meeting-points__tenant-actions {
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tenant-meeting-points__mode-panel {
|
||||
|
|
@ -199,6 +226,17 @@
|
|||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tenant-meeting-points__toolbar {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.tenant-meeting-points__filters,
|
||||
.tenant-meeting-points__filters .ant-space-item,
|
||||
.tenant-meeting-points__filter,
|
||||
.tenant-meeting-points__filter--wide {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.tenant-meeting-points__tenant-content {
|
||||
padding: 8px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -318,30 +318,32 @@ export default function TenantMeetingPointsSettings() {
|
|||
description="按租户控制会议积分是否校验余额,关闭后按无限余额模式记录消耗。"
|
||||
>
|
||||
<DataListPanel
|
||||
className="tenant-meeting-points__list-panel"
|
||||
toolbarClassName="tenant-meeting-points__toolbar"
|
||||
leftActions={
|
||||
<Button icon={<ReloadOutlined />} onClick={() => void handleRefresh()}>
|
||||
刷新
|
||||
</Button>
|
||||
}
|
||||
rightActions={
|
||||
<Space wrap>
|
||||
<Space wrap className="tenant-meeting-points__filters">
|
||||
<Input
|
||||
className="tenant-meeting-points__filter tenant-meeting-points__filter--wide"
|
||||
placeholder="按租户名称搜索"
|
||||
value={params.tenantName}
|
||||
onChange={(event) => setParams((prev) => ({ ...prev, tenantName: event.target.value }))}
|
||||
style={{ width: 220 }}
|
||||
prefix={<SearchOutlined className="text-gray-400" />}
|
||||
allowClear
|
||||
/>
|
||||
<Input
|
||||
className="tenant-meeting-points__filter"
|
||||
placeholder="按租户编码搜索"
|
||||
value={params.tenantCode}
|
||||
onChange={(event) => setParams((prev) => ({ ...prev, tenantCode: event.target.value }))}
|
||||
style={{ width: 180 }}
|
||||
allowClear
|
||||
/>
|
||||
<Select
|
||||
style={{ width: 180 }}
|
||||
className="tenant-meeting-points__filter"
|
||||
value={params.balanceCheckEnabled}
|
||||
onChange={(value) => setParams((prev) => ({ ...prev, balanceCheckEnabled: value }))}
|
||||
options={[
|
||||
|
|
@ -391,7 +393,7 @@ export default function TenantMeetingPointsSettings() {
|
|||
dataSource={records}
|
||||
loading={loading}
|
||||
totalCount={total}
|
||||
scroll={{x: 1200, y: "100%"}}
|
||||
scroll={{x: "max-content", y: "100%"}}
|
||||
pagination={false}
|
||||
/>
|
||||
</DataListPanel>
|
||||
|
|
|
|||
|
|
@ -362,7 +362,7 @@ export default function Devices() {
|
|||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
width={420}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
footer={
|
||||
<div className="app-page__drawer-footer">
|
||||
<Button onClick={() => setOpen(false)}>{t("common.cancel")}</Button>
|
||||
|
|
|
|||
|
|
@ -19,15 +19,18 @@
|
|||
|
||||
.home-shell-card {
|
||||
padding: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.home-shell-card .section-card__content {
|
||||
padding: 0;
|
||||
min-height: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.home-data-panel {
|
||||
padding: 0;
|
||||
min-height: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
|
@ -47,8 +50,8 @@
|
|||
position: relative;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
padding: clamp(40px, 8vh, 80px) clamp(24px, 5vw, 60px) 0;
|
||||
min-height: 0;
|
||||
padding: clamp(40px, 8vh, 80px) clamp(24px, 5vw, 60px) clamp(28px, 5vh, 48px);
|
||||
border-radius: 4px;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f1f5f9 100%);
|
||||
color: @home-text-main;
|
||||
|
|
@ -102,6 +105,7 @@
|
|||
width: 100%;
|
||||
margin: 0 auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
|
@ -322,7 +326,8 @@
|
|||
}
|
||||
|
||||
.home-recent-section {
|
||||
margin-top: 0;
|
||||
margin-top: 0;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.home-section-header {
|
||||
|
|
@ -510,6 +515,106 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media (max-height: 820px) {
|
||||
.home-container {
|
||||
padding-top: clamp(24px, 5vh, 40px);
|
||||
padding-bottom: clamp(24px, 4vh, 36px);
|
||||
|
||||
.home-content-wrapper {
|
||||
gap: clamp(18px, 3vh, 28px);
|
||||
}
|
||||
|
||||
.home-title {
|
||||
font-size: clamp(30px, 4vw, 48px) !important;
|
||||
margin-bottom: clamp(22px, 4vh, 36px) !important;
|
||||
}
|
||||
|
||||
.home-action-item {
|
||||
padding: 24px 22px;
|
||||
}
|
||||
|
||||
.home-action-item .home-action-icon-wrapper {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.home-action-item .home-action-title {
|
||||
font-size: 22px !important;
|
||||
margin-bottom: 12px !important;
|
||||
}
|
||||
|
||||
.home-section-header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.home-recent-card {
|
||||
min-height: 124px;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.home-recent-card .home-recent-card-footer {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 680px) {
|
||||
.home-container {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 28px;
|
||||
|
||||
.home-content-wrapper {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.home-title {
|
||||
font-size: clamp(28px, 3.6vw, 42px) !important;
|
||||
margin-bottom: 18px !important;
|
||||
}
|
||||
|
||||
.home-action-item {
|
||||
padding: 20px 18px;
|
||||
}
|
||||
|
||||
.home-action-item .home-action-icon-wrapper {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.home-action-item .home-action-title {
|
||||
font-size: 20px !important;
|
||||
margin-bottom: 8px !important;
|
||||
}
|
||||
|
||||
.home-action-item .home-action-line {
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.home-recent-card {
|
||||
min-height: 112px;
|
||||
padding: 14px 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.home-container {
|
||||
padding: 24px 18px 32px;
|
||||
|
||||
.home-title {
|
||||
font-size: clamp(28px, 9vw, 40px) !important;
|
||||
margin-bottom: 24px !important;
|
||||
}
|
||||
|
||||
.home-recent-card {
|
||||
min-height: 128px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-container > * {
|
||||
animation: homeFadeUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ export default function Orgs() {
|
|||
</div>
|
||||
</SectionCard>
|
||||
|
||||
<Drawer title={<Space><ApartmentOutlined aria-hidden="true" /><span>{editing ? t("orgs.drawerTitleEdit") : t("orgs.drawerTitleCreate")}</span></Space>} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={420} destroyOnClose footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>{t("common.cancel")}</Button><Button type="primary" loading={saving} onClick={submit}>{t("common.save")}</Button></div>}>
|
||||
<Drawer title={<Space><ApartmentOutlined aria-hidden="true" /><span>{editing ? t("orgs.drawerTitleEdit") : t("orgs.drawerTitleCreate")}</span></Space>} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={420} destroyOnHidden footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>{t("common.cancel")}</Button><Button type="primary" loading={saving} onClick={submit}>{t("common.save")}</Button></div>}>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item label={t("users.tenant")} name="tenantId" rules={[{ required: true }]} hidden={!isPlatformMode}>
|
||||
<Select disabled options={tenants.map((tenant) => ({ label: tenant.tenantName, value: tenant.id }))} />
|
||||
|
|
@ -210,13 +210,13 @@ export default function Orgs() {
|
|||
<Form.Item label={t("orgs.orgCode")} name="orgCode">
|
||||
<Input placeholder={t("orgsExt.orgCodePlaceholder")} className="tabular-nums" />
|
||||
</Form.Item>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t("dicts.sort")} name="sortOrder" initialValue={0}>
|
||||
<InputNumber style={{ width: "100%" }} min={0} className="tabular-nums" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t("common.status")} name="status" initialValue={1}>
|
||||
<Select options={statusDict.map((item) => ({ label: item.itemLabel, value: Number(item.itemValue) }))} />
|
||||
</Form.Item>
|
||||
|
|
|
|||
|
|
@ -246,17 +246,17 @@ export default function Tenants() {
|
|||
open={drawerOpen}
|
||||
onClose={() => setDrawerOpen(false)}
|
||||
width={480}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>{t("common.cancel")}</Button><Button type="primary" loading={saving} onClick={submit}>{t("common.save")}</Button></div>}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t("tenants.tenantName")} name="tenantName" rules={[{ required: true, message: t("tenants.tenantName") }]}>
|
||||
<Input placeholder={t("tenantsExt.tenantNamePlaceholder")} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t("tenants.tenantCode")} name="tenantCode" rules={[{ required: true, message: t("tenants.tenantCode") }]}>
|
||||
<Input placeholder={t("tenantsExt.tenantCodePlaceholder")} disabled={!!editing} className="tabular-nums" />
|
||||
</Form.Item>
|
||||
|
|
@ -285,13 +285,13 @@ export default function Tenants() {
|
|||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t("tenants.contactName")} name="contactName">
|
||||
<Input placeholder={t("tenantsExt.contactNamePlaceholder")} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t("tenants.contactPhone")} name="contactPhone">
|
||||
<Input placeholder={t("tenantsExt.contactPhonePlaceholder")} className="tabular-nums" />
|
||||
</Form.Item>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,17 @@
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.avatar-crop-modal .ant-modal-content {
|
||||
max-height: calc(100vh - 48px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.avatar-crop-modal .ant-modal-body {
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.avatar-crop-modal__layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.2fr) 320px;
|
||||
|
|
@ -116,3 +127,49 @@
|
|||
background: #f8fafc;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.avatar-crop-modal {
|
||||
max-width: calc(100vw - 24px);
|
||||
}
|
||||
|
||||
.avatar-crop-modal__layout {
|
||||
grid-template-columns: 1fr;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.avatar-crop-modal__stage {
|
||||
gap: 20px;
|
||||
padding: 24px 20px;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.avatar-crop-modal__viewport {
|
||||
width: min(300px, calc(100vw - 88px));
|
||||
height: min(300px, calc(100vw - 88px));
|
||||
}
|
||||
|
||||
.avatar-crop-modal__sidebar-card,
|
||||
.avatar-crop-modal__footer {
|
||||
padding-right: 20px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 680px) {
|
||||
.avatar-crop-modal .ant-modal-content {
|
||||
max-height: calc(100vh - 24px);
|
||||
}
|
||||
|
||||
.avatar-crop-modal__stage {
|
||||
gap: 16px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.avatar-crop-modal__viewport {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,3 +195,51 @@
|
|||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.dictionaries-page > .page-container__body,
|
||||
.dictionaries-page .section-card,
|
||||
.dictionaries-page .section-card__content {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.dictionaries-layout {
|
||||
flex: none;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
row-gap: 12px;
|
||||
}
|
||||
|
||||
.dictionaries-layout > .ant-col {
|
||||
height: auto;
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
.dictionaries-layout > .ant-col > .app-page__panel-card {
|
||||
height: min(430px, calc(100vh - 160px));
|
||||
min-height: 360px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.dictionaries-page {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.dict-type-search-row {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dict-type-search-row .ant-btn,
|
||||
.dict-type-search-input,
|
||||
.dict-type-search-input .ant-input,
|
||||
.dict-type-search-input .ant-input-group-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dictionaries-layout > .ant-col > .app-page__panel-card {
|
||||
height: min(390px, calc(100vh - 150px));
|
||||
min-height: 320px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ export default function Dictionaries() {
|
|||
description={t("dicts.subtitle")}
|
||||
>
|
||||
<Row gutter={16} className="dictionaries-layout">
|
||||
<Col span={8} className="h-full flex flex-col overflow-hidden">
|
||||
<Col xs={24} lg={8} className="h-full flex flex-col overflow-hidden">
|
||||
<Card title={<Space><BookOutlined aria-hidden="true"/><span>{t("dicts.dictType")}</span></Space>}
|
||||
className="app-page__panel-card flex-1 flex flex-col overflow-hidden" styles={{
|
||||
body: {
|
||||
|
|
@ -221,8 +221,8 @@ export default function Dictionaries() {
|
|||
<div className="dict-type-code text-xs text-gray-400 truncate tabular-nums">{record.typeCode}</div>
|
||||
</div>
|
||||
<div className="dict-type-actions flex gap-1">
|
||||
{can("sys_dict:type:update") && <Button type="text" size="small" icon={<EditOutlined aria-hidden="true" style={{ fontSize: "12px" }} />} onClick={(event) => { event.stopPropagation(); handleEditType(record); }} />}
|
||||
{can("sys_dict:type:delete") && <Popconfirm title={t("dictsExt.deleteType", { name: record.typeName })} okText={t("common.confirm")} cancelText={t("common.cancel")} onConfirm={(event) => { event?.stopPropagation(); handleDeleteType(record.dictTypeId); }}><Button type="text" size="small" danger icon={<DeleteOutlined aria-hidden="true" style={{ fontSize: "12px" }} />} onClick={(event) => event.stopPropagation()} /></Popconfirm>}
|
||||
{can("sys_dict:type:update") && <Button type="text" size="small" icon={<EditOutlined aria-hidden="true" style={{ fontSize: "12px" }} />} onClick={(event) => { event.stopPropagation(); handleEditType(record); }} aria-label={t("common.edit")} title={t("common.edit")} />}
|
||||
{can("sys_dict:type:delete") && <Popconfirm title={t("dictsExt.deleteType", { name: record.typeName })} okText={t("common.confirm")} cancelText={t("common.cancel")} onConfirm={(event) => { event?.stopPropagation(); handleDeleteType(record.dictTypeId); }}><Button type="text" size="small" danger icon={<DeleteOutlined aria-hidden="true" style={{ fontSize: "12px" }} />} onClick={(event) => event.stopPropagation()} aria-label={t("common.delete")} title={t("common.delete")} /></Popconfirm>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -233,7 +233,7 @@ export default function Dictionaries() {
|
|||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={16} className="h-full flex flex-col overflow-hidden">
|
||||
<Col xs={24} lg={16} className="h-full flex flex-col overflow-hidden">
|
||||
<Card title={<Space><ProfileOutlined aria-hidden="true" /><span>{t("dicts.dictItem")}{selectedType ? ` - ${selectedType.typeName}` : ""}</span></Space>} className="app-page__panel-card flex-1 flex flex-col overflow-hidden" styles={{ body: { padding: 12, flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" } }} extra={can("sys_dict:item:create") && <Button type="primary" size="small" icon={<PlusOutlined aria-hidden="true" />} onClick={handleAddItem} disabled={!selectedType}>{t("common.create")}</Button>}>
|
||||
{selectedType ? (
|
||||
<div className="dict-item-table-wrap">
|
||||
|
|
@ -281,7 +281,7 @@ export default function Dictionaries() {
|
|||
|
||||
<Drawer title={<Space><BookOutlined
|
||||
aria-hidden="true"/><span>{editingType ? t("dicts.drawerTitleTypeEdit") : t("dicts.drawerTitleTypeCreate")}</span></Space>}
|
||||
open={typeDrawerVisible} onClose={closeTypeDrawer} width={400} destroyOnClose
|
||||
open={typeDrawerVisible} onClose={closeTypeDrawer} width={400} destroyOnHidden
|
||||
footer={<div className="app-page__drawer-footer"><Button
|
||||
onClick={closeTypeDrawer}>{t("common.cancel")}</Button><Button type="primary"
|
||||
onClick={handleTypeSubmit}>{t("common.save")}</Button>
|
||||
|
|
@ -301,7 +301,7 @@ export default function Dictionaries() {
|
|||
|
||||
<Drawer title={<Space><ProfileOutlined
|
||||
aria-hidden="true"/><span>{editingItem ? t("dicts.drawerTitleItemEdit") : t("dicts.drawerTitleItemCreate")}</span></Space>}
|
||||
open={itemDrawerVisible} onClose={closeItemDrawer} width={400} destroyOnClose
|
||||
open={itemDrawerVisible} onClose={closeItemDrawer} width={400} destroyOnHidden
|
||||
footer={<div className="app-page__drawer-footer"><Button
|
||||
onClick={closeItemDrawer}>{t("common.cancel")}</Button><Button type="primary"
|
||||
onClick={handleItemSubmit}>{t("common.save")}</Button>
|
||||
|
|
|
|||
|
|
@ -241,10 +241,10 @@ export default function SysParams() {
|
|||
fixed: "right" as const,
|
||||
render: (_: any, record: SysParamVO) => (
|
||||
<Space>
|
||||
{can("sys_param:update") && <Button type="text" icon={<EditOutlined />} onClick={() => openEdit(record)} />}
|
||||
{can("sys_param:update") && <Button type="text" icon={<EditOutlined />} onClick={() => openEdit(record)} aria-label={t("common.edit")} title={t("common.edit")} />}
|
||||
{can("sys_param:delete") && record.isSystem !== 1 && (
|
||||
<Popconfirm title={t("sysParamsExt.deleteConfirm")} okText={t("common.confirm")} cancelText={t("common.cancel")} onConfirm={() => handleDelete(record.paramId)}>
|
||||
<Button type="text" danger icon={<DeleteOutlined />} />
|
||||
<Button type="text" danger icon={<DeleteOutlined />} aria-label={t("common.delete")} title={t("common.delete")} />
|
||||
</Popconfirm>
|
||||
)}
|
||||
</Space>
|
||||
|
|
@ -309,7 +309,7 @@ export default function SysParams() {
|
|||
open={drawerOpen}
|
||||
onClose={closeDrawer}
|
||||
width={500}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
footer={<div className="app-page__drawer-footer"><Button
|
||||
onClick={closeDrawer}>{t("common.cancel")}</Button><Button type="primary" loading={saving}
|
||||
onClick={submit}>{t("common.save")}</Button></div>}
|
||||
|
|
@ -318,8 +318,8 @@ export default function SysParams() {
|
|||
<Form.Item label={t("sysParams.paramKey")} name="paramKey" rules={[{ required: true, message: t("sysParams.paramKey") }]}>
|
||||
<Input placeholder={t("sysParamsExt.paramKeyPlaceholder")} disabled={!!editing} />
|
||||
</Form.Item>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Row gutter={16} className="app-responsive-form-row">
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t("sysParams.paramType")} name="paramType" rules={[{ required: true, message: t("sysParams.paramType") }]}>
|
||||
<Select placeholder={t("sysParams.paramType")} options={paramTypeDict.map((item) => ({ label: item.itemLabel, value: item.itemValue }))} onChange={(value) => {
|
||||
drawerForm.setFieldValue("paramValue", isType(value, "Boolean") ? "true" : "");
|
||||
|
|
@ -327,7 +327,7 @@ export default function SysParams() {
|
|||
}} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t("common.status")} name="status" initialValue={1}>
|
||||
<Select options={statusDict.map((item) => ({ label: item.itemLabel, value: Number(item.itemValue) }))} />
|
||||
</Form.Item>
|
||||
|
|
|
|||
Loading…
Reference in New Issue