feat: 界面优化1
parent
f5a6a22eb1
commit
cc43fe2e01
|
|
@ -177,6 +177,13 @@
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.data-list-panel__table-area .ant-table-thead > tr > th:last-child,
|
||||||
|
.data-list-panel__table-area .ant-table-tbody > tr > td:last-child,
|
||||||
|
.data-list-panel__table-area .ant-table-thead > tr > th.ant-table-cell-fix-right,
|
||||||
|
.data-list-panel__table-area .ant-table-tbody > tr > td.ant-table-cell-fix-right {
|
||||||
|
padding-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.data-list-panel__table-area .ant-table-content,
|
.data-list-panel__table-area .ant-table-content,
|
||||||
.data-list-panel__table-area .ant-table-body {
|
.data-list-panel__table-area .ant-table-body {
|
||||||
overflow-x: auto !important;
|
overflow-x: auto !important;
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,24 @@
|
||||||
border-bottom: 1px solid var(--app-border-color, #f0f0f0);
|
border-bottom: 1px solid var(--app-border-color, #f0f0f0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-table-container .ant-table-thead > tr > th:last-child,
|
||||||
|
.list-table-container .ant-table-tbody > tr > td:last-child,
|
||||||
|
.list-table-container .ant-table-thead > tr > th.ant-table-cell-fix-right,
|
||||||
|
.list-table-container .ant-table-tbody > tr > td.ant-table-cell-fix-right {
|
||||||
|
padding-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-table-container .ant-table-thead > tr > th:last-child,
|
||||||
|
.list-table-container .ant-table-tbody > tr > td:last-child {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-table-container .ant-table-tbody > tr > td:last-child .ant-space,
|
||||||
|
.list-table-container .ant-table-tbody > tr > td.ant-table-cell-fix-right .ant-space {
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.list-table-container .ant-table-tbody > tr:not(.row-selected):not(.ant-table-row-selected):hover > td {
|
.list-table-container .ant-table-tbody > tr:not(.row-selected):not(.ant-table-row-selected):hover > td {
|
||||||
background: var(--app-surface-color, #fff) !important;
|
background: var(--app-surface-color, #fff) !important;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
.summary-stat-cards {
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-stat-cards__card {
|
||||||
|
height: 100%;
|
||||||
|
min-height: 112px;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-stat-cards__card .ant-card-body {
|
||||||
|
height: 100%;
|
||||||
|
padding: 26px 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-stat-cards__label {
|
||||||
|
color: var(--app-text-secondary, #9095a1);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-stat-cards__card .ant-statistic-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-stat-cards__icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import type { CSSProperties, ReactNode } from "react";
|
||||||
|
import { Card, Col, Row, Statistic } from "antd";
|
||||||
|
import "./SummaryStatCards.css";
|
||||||
|
|
||||||
|
export interface SummaryStatCardItem {
|
||||||
|
key: string;
|
||||||
|
label: ReactNode;
|
||||||
|
value: string | number;
|
||||||
|
icon: ReactNode;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SummaryStatCardsProps {
|
||||||
|
items: SummaryStatCardItem[];
|
||||||
|
ariaLabel?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SummaryStatCards({ items, ariaLabel }: SummaryStatCardsProps) {
|
||||||
|
return (
|
||||||
|
<div className="summary-stat-cards" aria-label={ariaLabel}>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
{items.map((item) => (
|
||||||
|
<Col xs={24} sm={12} xl={6} key={item.key}>
|
||||||
|
<Card className="summary-stat-cards__card" variant="borderless">
|
||||||
|
<Statistic
|
||||||
|
title={<span className="summary-stat-cards__label">{item.label}</span>}
|
||||||
|
value={item.value}
|
||||||
|
valueStyle={{ color: item.color, fontWeight: 700 } as CSSProperties}
|
||||||
|
prefix={
|
||||||
|
<span className="summary-stat-cards__icon" style={{ color: item.color }}>
|
||||||
|
{item.icon}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -680,8 +680,18 @@ export default function Users() {
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
) : null}
|
) : null}
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col span={12}><Form.Item label={t("common.status")} name="status" initialValue={1}><Select
|
<Col span={12}>
|
||||||
options={statusDict.map((item) => ({label: item.itemLabel, value: Number(item.itemValue)}))}/></Form.Item></Col>
|
<Form.Item
|
||||||
|
label={t("common.status")}
|
||||||
|
name="status"
|
||||||
|
initialValue={1}
|
||||||
|
valuePropName="checked"
|
||||||
|
getValueProps={(value) => ({ checked: value !== 0 })}
|
||||||
|
getValueFromEvent={(checked: boolean) => (checked ? 1 : 0)}
|
||||||
|
>
|
||||||
|
<Switch checkedChildren={t("usersExt.enabled")} unCheckedChildren={t("usersExt.disabled")} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
{isPlatformMode &&
|
{isPlatformMode &&
|
||||||
<Col span={12}><Form.Item label={t("users.platformAdmin")} name="isPlatformAdmin" valuePropName="checked"><Switch/></Form.Item></Col>}
|
<Col span={12}><Form.Item label={t("users.platformAdmin")} name="isPlatformAdmin" valuePropName="checked"><Switch/></Form.Item></Col>}
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
||||||
|
|
@ -12,42 +12,7 @@
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-summary-chips {
|
.client-management-page__content {
|
||||||
display: flex;
|
gap: 12px;
|
||||||
align-items: center;
|
padding: 8px;
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.client-summary-chip {
|
|
||||||
height: 32px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 0 10px;
|
|
||||||
border: 1px solid #e6e6e6;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: #f9fafe;
|
|
||||||
color: #333;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 20px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.client-summary-chip .anticon {
|
|
||||||
color: #3c70f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.client-summary-chip strong {
|
|
||||||
color: #1677ff;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.client-summary-chips,
|
|
||||||
.client-summary-chip {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import DataListPanel from "@/components/shared/DataListPanel";
|
||||||
import ListTable from "@/components/shared/ListTable/ListTable";
|
import ListTable from "@/components/shared/ListTable/ListTable";
|
||||||
import AppPagination from "@/components/shared/AppPagination";
|
import AppPagination from "@/components/shared/AppPagination";
|
||||||
import SectionCard from "@/components/shared/SectionCard";
|
import SectionCard from "@/components/shared/SectionCard";
|
||||||
|
import SummaryStatCards from "@/components/shared/SummaryStatCards";
|
||||||
import { createClientDownload, deleteClientDownload, listClientDownloads, type ClientDownloadDTO, type ClientDownloadVO, updateClientDownload, uploadClientPackage } from "@/api/business/client";
|
import { createClientDownload, deleteClientDownload, listClientDownloads, type ClientDownloadDTO, type ClientDownloadVO, updateClientDownload, uploadClientPackage } from "@/api/business/client";
|
||||||
import { fetchDictItemsByTypeCode } from "@/api/dict";
|
import { fetchDictItemsByTypeCode } from "@/api/dict";
|
||||||
import { useDict } from "@/hooks/useDict";
|
import { useDict } from "@/hooks/useDict";
|
||||||
|
|
@ -236,6 +237,13 @@ export default function ClientManagement() {
|
||||||
groups: platformGroups.length,
|
groups: platformGroups.length,
|
||||||
}), [platformGroups.length, records]);
|
}), [platformGroups.length, records]);
|
||||||
|
|
||||||
|
const statCards = useMemo(() => [
|
||||||
|
{ key: "total", label: "发布总数", value: stats.total, icon: <CloudUploadOutlined />, color: "#1890ff" },
|
||||||
|
{ key: "enabled", label: "已启用", value: stats.enabled, icon: <LaptopOutlined />, color: "#faad14" },
|
||||||
|
{ key: "latest", label: "最新版本", value: stats.latest, icon: <MobileOutlined />, color: "#52c41a" },
|
||||||
|
{ key: "groups", label: "平台分组", value: stats.groups, icon: <RocketOutlined />, color: "#13c2c2" },
|
||||||
|
], [stats]);
|
||||||
|
|
||||||
const openCreate = () => {
|
const openCreate = () => {
|
||||||
const firstOption = platformGroups[0]?.options[0];
|
const firstOption = platformGroups[0]?.options[0];
|
||||||
if (!firstOption) {
|
if (!firstOption) {
|
||||||
|
|
@ -410,20 +418,14 @@ export default function ClientManagement() {
|
||||||
<SectionCard
|
<SectionCard
|
||||||
title="客户端管理"
|
title="客户端管理"
|
||||||
description="发布平台由数据字典 client_platform 驱动,并按父子分组展示。"
|
description="发布平台由数据字典 client_platform 驱动,并按父子分组展示。"
|
||||||
|
contentClassName="client-management-page__content"
|
||||||
>
|
>
|
||||||
|
<SummaryStatCards items={statCards} ariaLabel="客户端发布统计" />
|
||||||
<DataListPanel
|
<DataListPanel
|
||||||
leftActions={
|
leftActions={
|
||||||
<Space wrap>
|
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
||||||
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
发布新版
|
||||||
发布新版
|
</Button>
|
||||||
</Button>
|
|
||||||
<div className="client-summary-chips" aria-label="客户端发布统计">
|
|
||||||
<span className="client-summary-chip"><CloudUploadOutlined aria-hidden="true" /><span>发布总数</span><strong>{stats.total}</strong></span>
|
|
||||||
<span className="client-summary-chip"><LaptopOutlined aria-hidden="true" /><span>已启用</span><strong>{stats.enabled}</strong></span>
|
|
||||||
<span className="client-summary-chip"><MobileOutlined aria-hidden="true" /><span>最新版本</span><strong>{stats.latest}</strong></span>
|
|
||||||
<span className="client-summary-chip"><RocketOutlined aria-hidden="true" /><span>平台分组</span><strong>{stats.groups}</strong></span>
|
|
||||||
</div>
|
|
||||||
</Space>
|
|
||||||
}
|
}
|
||||||
rightActions={
|
rightActions={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
|
|
||||||
|
|
@ -12,37 +12,9 @@
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.external-app-summary-chips {
|
.external-app-page__content {
|
||||||
display: flex;
|
gap: 12px;
|
||||||
align-items: center;
|
padding: 8px;
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.external-app-summary-chip {
|
|
||||||
height: 32px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 0 10px;
|
|
||||||
border: 1px solid #e6e6e6;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: #f9fafe;
|
|
||||||
color: #333;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 20px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.external-app-summary-chip .anticon {
|
|
||||||
color: #3c70f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.external-app-summary-chip strong {
|
|
||||||
color: #1677ff;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.external-app-icon-form-item {
|
.external-app-icon-form-item {
|
||||||
|
|
@ -133,11 +105,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.external-app-summary-chips,
|
|
||||||
.external-app-summary-chip {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.external-app-icon-picker {
|
.external-app-icon-picker {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 116px;
|
min-height: 116px;
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,13 @@ import DataListPanel from "@/components/shared/DataListPanel";
|
||||||
import ListTable from "@/components/shared/ListTable/ListTable";
|
import ListTable from "@/components/shared/ListTable/ListTable";
|
||||||
import AppPagination from "@/components/shared/AppPagination";
|
import AppPagination from "@/components/shared/AppPagination";
|
||||||
import SectionCard from "@/components/shared/SectionCard";
|
import SectionCard from "@/components/shared/SectionCard";
|
||||||
|
import SummaryStatCards from "@/components/shared/SummaryStatCards";
|
||||||
import { createExternalApp, deleteExternalApp, listExternalApps, type ExternalAppDTO, type ExternalAppVO, updateExternalApp, uploadExternalAppApk, uploadExternalAppIcon } from "@/api/business/externalApp";
|
import { createExternalApp, deleteExternalApp, listExternalApps, type ExternalAppDTO, type ExternalAppVO, updateExternalApp, uploadExternalAppApk, uploadExternalAppIcon } from "@/api/business/externalApp";
|
||||||
import "./ExternalAppManagement.css";
|
import "./ExternalAppManagement.css";
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
|
const ICON_UPLOAD_ACCEPT = ".png,.jpg,.jpeg,image/png,image/jpeg";
|
||||||
|
|
||||||
type ExternalAppFormValues = {
|
type ExternalAppFormValues = {
|
||||||
appName: string;
|
appName: string;
|
||||||
|
|
@ -121,6 +123,13 @@ export default function ExternalAppManagement() {
|
||||||
enabled: records.filter((item) => item.status === 1).length,
|
enabled: records.filter((item) => item.status === 1).length,
|
||||||
}), [records]);
|
}), [records]);
|
||||||
|
|
||||||
|
const statCards = useMemo(() => [
|
||||||
|
{ key: "total", label: "应用总数", value: stats.total, icon: <AppstoreOutlined />, color: "#1890ff" },
|
||||||
|
{ key: "native", label: "原生应用", value: stats.native, icon: <RobotOutlined />, color: "#faad14" },
|
||||||
|
{ key: "web", label: "Web 应用", value: stats.web, icon: <GlobalOutlined />, color: "#52c41a" },
|
||||||
|
{ key: "enabled", label: "已启用", value: stats.enabled, icon: <PictureOutlined />, color: "#13c2c2" },
|
||||||
|
], [stats]);
|
||||||
|
|
||||||
const openCreate = () => {
|
const openCreate = () => {
|
||||||
setEditing(null);
|
setEditing(null);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
|
|
@ -218,6 +227,20 @@ export default function ExternalAppManagement() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const beforeUploadIcon = (file: File) => {
|
||||||
|
const isAllowedIcon = file.type
|
||||||
|
? file.type === "image/png" || file.type === "image/jpeg"
|
||||||
|
: /\.(png|jpe?g)$/i.test(file.name);
|
||||||
|
|
||||||
|
if (!isAllowedIcon) {
|
||||||
|
message.warning("应用图标仅支持上传 PNG / JPG 格式");
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleUploadIcon(file);
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
|
};
|
||||||
|
|
||||||
const handleToggleStatus = async (record: ExternalAppVO, checked: boolean) => {
|
const handleToggleStatus = async (record: ExternalAppVO, checked: boolean) => {
|
||||||
await updateExternalApp(record.id, { status: checked ? 1 : 0 });
|
await updateExternalApp(record.id, { status: checked ? 1 : 0 });
|
||||||
message.success(checked ? "已启用应用" : "已停用应用");
|
message.success(checked ? "已启用应用" : "已停用应用");
|
||||||
|
|
@ -299,20 +322,14 @@ export default function ExternalAppManagement() {
|
||||||
<SectionCard
|
<SectionCard
|
||||||
title="外部应用管理"
|
title="外部应用管理"
|
||||||
description="统一维护首页九宫格与抽屉入口中的原生应用、Web 服务和应用图标资源。"
|
description="统一维护首页九宫格与抽屉入口中的原生应用、Web 服务和应用图标资源。"
|
||||||
|
contentClassName="external-app-page__content"
|
||||||
>
|
>
|
||||||
|
<SummaryStatCards items={statCards} ariaLabel="外部应用统计" />
|
||||||
<DataListPanel
|
<DataListPanel
|
||||||
leftActions={
|
leftActions={
|
||||||
<Space wrap>
|
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
||||||
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
新增应用
|
||||||
新增应用
|
</Button>
|
||||||
</Button>
|
|
||||||
<div className="external-app-summary-chips" aria-label="外部应用统计">
|
|
||||||
<span className="external-app-summary-chip"><AppstoreOutlined aria-hidden="true" /><span>应用总数</span><strong>{stats.total}</strong></span>
|
|
||||||
<span className="external-app-summary-chip"><RobotOutlined aria-hidden="true" /><span>原生应用</span><strong>{stats.native}</strong></span>
|
|
||||||
<span className="external-app-summary-chip"><GlobalOutlined aria-hidden="true" /><span>Web 应用</span><strong>{stats.web}</strong></span>
|
|
||||||
<span className="external-app-summary-chip"><PictureOutlined aria-hidden="true" /><span>已启用</span><strong>{stats.enabled}</strong></span>
|
|
||||||
</div>
|
|
||||||
</Space>
|
|
||||||
}
|
}
|
||||||
rightActions={
|
rightActions={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
|
@ -360,9 +377,9 @@ export default function ExternalAppManagement() {
|
||||||
|
|
||||||
<Form.Item label="应用图标" className="external-app-icon-form-item">
|
<Form.Item label="应用图标" className="external-app-icon-form-item">
|
||||||
<Upload
|
<Upload
|
||||||
accept="image/png,image/jpeg,image/webp,image/svg+xml"
|
accept={ICON_UPLOAD_ACCEPT}
|
||||||
showUploadList={false}
|
showUploadList={false}
|
||||||
beforeUpload={(file) => { void handleUploadIcon(file as File); return Upload.LIST_IGNORE; }}
|
beforeUpload={(file) => beforeUploadIcon(file as File)}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -380,7 +397,7 @@ export default function ExternalAppManagement() {
|
||||||
<span className="external-app-icon-picker__text">
|
<span className="external-app-icon-picker__text">
|
||||||
{uploadingIcon ? "上传中..." : watchedIconUrl ? "更换图标" : "点击上传图标"}
|
{uploadingIcon ? "上传中..." : watchedIconUrl ? "更换图标" : "点击上传图标"}
|
||||||
</span>
|
</span>
|
||||||
<span className="external-app-icon-picker__hint">PNG / JPG / WebP / SVG</span>
|
<span className="external-app-icon-picker__hint">PNG / JPG</span>
|
||||||
</button>
|
</button>
|
||||||
</Upload>
|
</Upload>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
|
||||||
|
|
@ -12,42 +12,7 @@
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.license-summary-chips {
|
.license-management-page__content {
|
||||||
display: flex;
|
gap: 12px;
|
||||||
align-items: center;
|
padding: 8px;
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.license-summary-chip {
|
|
||||||
height: 32px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 0 10px;
|
|
||||||
border: 1px solid #e6e6e6;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: #f9fafe;
|
|
||||||
color: #333;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 20px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.license-summary-chip .anticon {
|
|
||||||
color: #3c70f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.license-summary-chip strong {
|
|
||||||
color: #1677ff;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.license-summary-chips,
|
|
||||||
.license-summary-chip {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import AppPagination from "@/components/shared/AppPagination";
|
||||||
import DataListPanel from "@/components/shared/DataListPanel";
|
import DataListPanel from "@/components/shared/DataListPanel";
|
||||||
import ListTable from "@/components/shared/ListTable/ListTable";
|
import ListTable from "@/components/shared/ListTable/ListTable";
|
||||||
import SectionCard from "@/components/shared/SectionCard";
|
import SectionCard from "@/components/shared/SectionCard";
|
||||||
|
import SummaryStatCards from "@/components/shared/SummaryStatCards";
|
||||||
import { listLicenses, type LicenseVO } from "@/api/business/license";
|
import { listLicenses, type LicenseVO } from "@/api/business/license";
|
||||||
import "./LicenseManagement.css";
|
import "./LicenseManagement.css";
|
||||||
|
|
||||||
|
|
@ -92,6 +93,13 @@ export default function LicenseManagement() {
|
||||||
available: records.filter((item) => item.licenseStatus === 1).length,
|
available: records.filter((item) => item.licenseStatus === 1).length,
|
||||||
}), [records]);
|
}), [records]);
|
||||||
|
|
||||||
|
const statCards = useMemo(() => [
|
||||||
|
{ key: "total", label: "授权总数", value: stats.total, icon: <KeyOutlined />, color: "#1890ff" },
|
||||||
|
{ key: "using", label: "使用中", value: stats.using, icon: <LinkOutlined />, color: "#faad14" },
|
||||||
|
{ key: "formal", label: "正式授权", value: stats.formal, icon: <CheckCircleOutlined />, color: "#52c41a" },
|
||||||
|
{ key: "available", label: "可分配", value: stats.available, icon: <ClockCircleOutlined />, color: "#13c2c2" },
|
||||||
|
], [stats]);
|
||||||
|
|
||||||
const columns: ColumnsType<LicenseVO> = [
|
const columns: ColumnsType<LicenseVO> = [
|
||||||
{
|
{
|
||||||
title: "授权标识",
|
title: "授权标识",
|
||||||
|
|
@ -139,32 +147,10 @@ export default function LicenseManagement() {
|
||||||
<SectionCard
|
<SectionCard
|
||||||
title="授权码管理"
|
title="授权码管理"
|
||||||
description="查看当前租户授权池"
|
description="查看当前租户授权池"
|
||||||
|
contentClassName="license-management-page__content"
|
||||||
>
|
>
|
||||||
|
<SummaryStatCards items={statCards} ariaLabel="授权码统计" />
|
||||||
<DataListPanel
|
<DataListPanel
|
||||||
leftActions={
|
|
||||||
<div className="license-summary-chips" aria-label="授权码统计">
|
|
||||||
<span className="license-summary-chip">
|
|
||||||
<KeyOutlined aria-hidden="true" />
|
|
||||||
<span>授权总数</span>
|
|
||||||
<strong className="tabular-nums">{stats.total}</strong>
|
|
||||||
</span>
|
|
||||||
<span className="license-summary-chip">
|
|
||||||
<LinkOutlined aria-hidden="true" />
|
|
||||||
<span>使用中</span>
|
|
||||||
<strong className="tabular-nums">{stats.using}</strong>
|
|
||||||
</span>
|
|
||||||
<span className="license-summary-chip">
|
|
||||||
<CheckCircleOutlined aria-hidden="true" />
|
|
||||||
<span>正式授权</span>
|
|
||||||
<strong className="tabular-nums">{stats.formal}</strong>
|
|
||||||
</span>
|
|
||||||
<span className="license-summary-chip">
|
|
||||||
<ClockCircleOutlined aria-hidden="true" />
|
|
||||||
<span>可分配</span>
|
|
||||||
<strong className="tabular-nums">{stats.available}</strong>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
rightActions={
|
rightActions={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Input
|
<Input
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,91 @@
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.meeting-detail-section-card .section-card__header {
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-section-card .section-card__title-wrap {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.meeting-detail-section-card .section-card__title {
|
.meeting-detail-section-card .section-card__title {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
padding-bottom: 14px;
|
||||||
|
align-items: flex-start;
|
||||||
|
overflow: visible;
|
||||||
|
white-space: normal;
|
||||||
|
text-overflow: clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-section-card .section-card__title::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .section-card__extra {
|
||||||
|
align-self: flex-start;
|
||||||
|
padding-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .section-card__extra .ant-space {
|
||||||
|
justify-content: flex-end;
|
||||||
|
row-gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .section-card__extra .ant-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .section-card__extra .ant-btn .anticon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .meeting-detail-title-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .meeting-detail-title-icon {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
flex: 0 0 42px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .meeting-detail-title-icon .anticon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .meeting-detail-title-copy {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .meeting-detail-title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meeting-detail-section-title {
|
.meeting-detail-section-title {
|
||||||
|
|
@ -33,6 +116,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.meeting-detail-page-v2 .meeting-detail-title-text {
|
.meeting-detail-page-v2 .meeting-detail-title-text {
|
||||||
|
display: block;
|
||||||
|
flex: 0 1 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: #333;
|
color: #333;
|
||||||
|
|
@ -60,12 +145,55 @@
|
||||||
.meeting-detail-page-v2 .meeting-detail-meta-row {
|
.meeting-detail-page-v2 .meeting-detail-meta-row {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 10px 16px;
|
align-items: center;
|
||||||
|
gap: 8px 16px;
|
||||||
color: #9095a1;
|
color: #9095a1;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .meeting-detail-meta-row span {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
min-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .meeting-detail-meta-row .anticon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .meeting-detail-config-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px 10px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .meeting-detail-config-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border: 1px solid #d8e3ff;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #f3f7ff;
|
||||||
|
color: #58627f;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .meeting-detail-config-item strong {
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.meeting-detail-section-content {
|
.meeting-detail-section-content {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
@ -422,6 +550,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
|
.meeting-detail-section-card .section-card__header {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .section-card__extra,
|
||||||
|
.meeting-detail-page-v2 .section-card__extra .ant-space {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.meeting-detail-section-content {
|
.meeting-detail-section-content {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
@ -438,13 +576,190 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.meeting-detail-page-v2 .section-card__extra,
|
.meeting-detail-page-v2 .meeting-detail-title-wrap {
|
||||||
.meeting-detail-page-v2 .section-card__extra .ant-space {
|
gap: 12px;
|
||||||
width: 100%;
|
}
|
||||||
justify-content: flex-start;
|
|
||||||
|
.meeting-detail-page-v2 .meeting-detail-title-icon {
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
flex-basis: 38px;
|
||||||
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meeting-detail-page-v2 .meeting-detail-title-text {
|
.meeting-detail-page-v2 .meeting-detail-title-text {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .meeting-detail-title-row {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .meeting-detail-config-item {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .detail-right-column {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-player-anchor > .transcript-workspace-card,
|
||||||
|
.meeting-detail-page-v2 .transcript-player-anchor > .transcript-workspace-card.left-flow-card,
|
||||||
|
.meeting-detail-page-v2 .transcript-player-anchor > .transcript-workspace-card.ant-card {
|
||||||
|
overflow: hidden !important;
|
||||||
|
border: 1px solid #e5e7eb !important;
|
||||||
|
border-radius: 18px !important;
|
||||||
|
background: #ffffff !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-panel-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-height: 56px;
|
||||||
|
padding: 0 24px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-panel-header .transcript-stage-tabs {
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center;
|
||||||
|
align-self: stretch;
|
||||||
|
gap: 24px !important;
|
||||||
|
min-height: 56px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
border: 0 !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-panel-header .transcript-stage-tabs button {
|
||||||
|
position: relative;
|
||||||
|
height: 56px !important;
|
||||||
|
min-width: 64px;
|
||||||
|
padding: 0 !important;
|
||||||
|
border: 0 !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
color: #4b5563 !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-panel-header .transcript-stage-tabs button + button {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-panel-header .transcript-stage-tabs button:hover {
|
||||||
|
color: #3c70f5 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-panel-header .transcript-stage-tabs button.active {
|
||||||
|
color: #2f5edb !important;
|
||||||
|
background: transparent !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-panel-header .transcript-stage-tabs button.active::after {
|
||||||
|
content: "" !important;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 3px;
|
||||||
|
border-radius: 999px 999px 0 0;
|
||||||
|
background: #6258ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-scroll-shell {
|
||||||
|
padding: 24px 20px 18px 18px !important;
|
||||||
|
background: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-player.transcript-player--floating {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 14px 16px !important;
|
||||||
|
border: 0 !important;
|
||||||
|
border-radius: 18px !important;
|
||||||
|
background: rgba(255, 255, 255, 0.96) !important;
|
||||||
|
box-shadow: 0 16px 40px rgba(20, 35, 70, 0.16) !important;
|
||||||
|
backdrop-filter: blur(12px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-player .player-main-btn {
|
||||||
|
width: 46px !important;
|
||||||
|
height: 46px !important;
|
||||||
|
flex: 0 0 46px;
|
||||||
|
border: 0 !important;
|
||||||
|
border-radius: 50% !important;
|
||||||
|
background: linear-gradient(135deg, #3c70f5 0%, #6b8cff 100%) !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
box-shadow: 0 10px 24px rgba(60, 112, 245, 0.32) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-player .player-ghost-btn {
|
||||||
|
height: 36px !important;
|
||||||
|
border: 1px solid #dce6ff !important;
|
||||||
|
border-radius: 999px !important;
|
||||||
|
background: #f4f7ff !important;
|
||||||
|
color: #3c70f5 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-player .player-progress-shell {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-player .player-range {
|
||||||
|
height: 6px !important;
|
||||||
|
border-radius: 999px !important;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
#3c70f5 0%,
|
||||||
|
#3c70f5 var(--player-progress, 0%),
|
||||||
|
#e6ebf5 var(--player-progress, 0%),
|
||||||
|
#e6ebf5 100%
|
||||||
|
) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-player .player-range::-webkit-slider-thumb {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 3px solid #ffffff !important;
|
||||||
|
border-radius: 50% !important;
|
||||||
|
background: #3c70f5 !important;
|
||||||
|
box-shadow: 0 4px 10px rgba(60, 112, 245, 0.28) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-player .player-range::-moz-range-thumb {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 3px solid #ffffff !important;
|
||||||
|
border-radius: 50% !important;
|
||||||
|
background: #3c70f5 !important;
|
||||||
|
box-shadow: 0 4px 10px rgba(60, 112, 245, 0.28) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.meeting-detail-page-v2 .transcript-panel-header {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-panel-header .transcript-stage-tabs {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-detail-page-v2 .transcript-panel-header .transcript-stage-tabs button {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,8 @@ const resolveAudioExtension = (audioUrl?: string) => {
|
||||||
return normalizedUrl.match(/\.([a-z0-9]+)$/i)?.[1]?.toLowerCase() || 'mp3';
|
return normalizedUrl.match(/\.([a-z0-9]+)$/i)?.[1]?.toLowerCase() || 'mp3';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeTagDisplay = (tag: string) => tag.replace(/^#+\s*/, '').trim();
|
||||||
|
|
||||||
const getMeetingAudioDownloadName = (meeting?: Pick<MeetingVO, 'title' | 'audioUrl' | 'playbackAudioUrl'> | null) => {
|
const getMeetingAudioDownloadName = (meeting?: Pick<MeetingVO, 'title' | 'audioUrl' | 'playbackAudioUrl'> | null) => {
|
||||||
const audioUrl = resolveMeetingPlaybackAudioUrl(meeting);
|
const audioUrl = resolveMeetingPlaybackAudioUrl(meeting);
|
||||||
const extension = resolveAudioExtension(audioUrl);
|
const extension = resolveAudioExtension(audioUrl);
|
||||||
|
|
@ -2357,6 +2359,10 @@ const MeetingDetail: React.FC = () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const playerProgressPercent = audioDuration > 0
|
||||||
|
? Math.min(100, Math.max(0, (audioCurrentTime / audioDuration) * 100))
|
||||||
|
: 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer title={null} className="meeting-detail-page-v2">
|
<PageContainer title={null} className="meeting-detail-page-v2">
|
||||||
<SectionCard
|
<SectionCard
|
||||||
|
|
@ -2380,7 +2386,9 @@ const MeetingDetail: React.FC = () => {
|
||||||
<span>
|
<span>
|
||||||
<ClockCircleOutlined /> {dayjs(meeting.meetingTime).format('YYYY-MM-DD HH:mm')}
|
<ClockCircleOutlined /> {dayjs(meeting.meetingTime).format('YYYY-MM-DD HH:mm')}
|
||||||
</span>
|
</span>
|
||||||
<span>{meeting.participants || '未指定'}</span>
|
<span>
|
||||||
|
<UserOutlined /> {meeting.participants || '未指定'}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="meeting-detail-config-row">
|
<div className="meeting-detail-config-row">
|
||||||
<span className="meeting-detail-config-item">
|
<span className="meeting-detail-config-item">
|
||||||
|
|
@ -2616,7 +2624,7 @@ const MeetingDetail: React.FC = () => {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>#{tag}</span>
|
<span>{normalizeTagDisplay(tag)}</span>
|
||||||
{isOwner && isSelected && <CheckCircleFilled style={{ fontSize: 12 }} />}
|
{isOwner && isSelected && <CheckCircleFilled style={{ fontSize: 12 }} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -2773,23 +2781,29 @@ const MeetingDetail: React.FC = () => {
|
||||||
</audio>
|
</audio>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="transcript-stage-tabs">
|
<div className="transcript-panel-header">
|
||||||
{aiCatalogEnabled && (
|
<div className="transcript-stage-tabs" role="tablist" aria-label="内容切换">
|
||||||
|
{aiCatalogEnabled && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected={workspaceTab === 'catalog'}
|
||||||
|
className={workspaceTab === 'catalog' ? 'active' : ''}
|
||||||
|
onClick={() => setWorkspaceTab('catalog')}
|
||||||
|
>
|
||||||
|
AI目录
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={workspaceTab === 'catalog' ? 'active' : ''}
|
role="tab"
|
||||||
onClick={() => setWorkspaceTab('catalog')}
|
aria-selected={workspaceTab === 'transcript'}
|
||||||
|
className={workspaceTab === 'transcript' ? 'active' : ''}
|
||||||
|
onClick={() => setWorkspaceTab('transcript')}
|
||||||
>
|
>
|
||||||
AI目录
|
转录原文
|
||||||
</button>
|
</button>
|
||||||
)}
|
</div>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={workspaceTab === 'transcript' ? 'active' : ''}
|
|
||||||
onClick={() => setWorkspaceTab('transcript')}
|
|
||||||
>
|
|
||||||
转录原文
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="transcript-scroll-shell">
|
<div className="transcript-scroll-shell">
|
||||||
|
|
@ -2920,6 +2934,7 @@ const MeetingDetail: React.FC = () => {
|
||||||
max={audioDuration || 0}
|
max={audioDuration || 0}
|
||||||
step={0.1}
|
step={0.1}
|
||||||
value={Math.min(audioCurrentTime, audioDuration || 0)}
|
value={Math.min(audioCurrentTime, audioDuration || 0)}
|
||||||
|
style={{ '--player-progress': `${playerProgressPercent}%` } as React.CSSProperties}
|
||||||
onChange={handleAudioProgressChange}
|
onChange={handleAudioProgressChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -2956,11 +2971,10 @@ const MeetingDetail: React.FC = () => {
|
||||||
}
|
}
|
||||||
/* 当转录行处于活动状态时,调整高亮样式以保持可读性 */
|
/* 当转录行处于活动状态时,调整高亮样式以保持可读性 */
|
||||||
.ant-list-item.transcript-row.active .highlight-text {
|
.ant-list-item.transcript-row.active .highlight-text {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: linear-gradient(120deg, rgba(95, 81, 255, 0.15) 0%, rgba(108, 140, 255, 0.1) 100%);
|
||||||
border-bottom-color: #fff;
|
border-bottom-color: #5f51ff;
|
||||||
color: #fff;
|
color: #4335eb;
|
||||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
text-shadow: none;
|
||||||
animation: none; /* 活动行内不需要闪烁,避免视觉混乱 */
|
|
||||||
}
|
}
|
||||||
.summary-keyword-link {
|
.summary-keyword-link {
|
||||||
color: #5f51ff;
|
color: #5f51ff;
|
||||||
|
|
@ -3905,16 +3919,17 @@ const MeetingDetail: React.FC = () => {
|
||||||
}
|
}
|
||||||
.ant-list-item.transcript-row.linked .transcript-bubble,
|
.ant-list-item.transcript-row.linked .transcript-bubble,
|
||||||
.ant-list-item.transcript-row.linked .transcript-bubble-editing {
|
.ant-list-item.transcript-row.linked .transcript-bubble-editing {
|
||||||
background: linear-gradient(135deg, rgba(95, 81, 255, 0.08), rgba(108, 140, 255, 0.06));
|
background: #ffffff;
|
||||||
border-color: rgba(95, 81, 255, 0.2);
|
border-color: rgba(60, 112, 245, 0.28);
|
||||||
box-shadow: 0 10px 24px rgba(95, 81, 255, 0.06);
|
color: #2d3553;
|
||||||
|
box-shadow: 0 10px 24px rgba(60, 112, 245, 0.08);
|
||||||
}
|
}
|
||||||
.ant-list-item.transcript-row.active .transcript-bubble,
|
.ant-list-item.transcript-row.active .transcript-bubble,
|
||||||
.ant-list-item.transcript-row.active .transcript-bubble-editing {
|
.ant-list-item.transcript-row.active .transcript-bubble-editing {
|
||||||
border-color: rgba(95, 81, 255, 0.16);
|
border-color: rgba(60, 112, 245, 0.34);
|
||||||
background: linear-gradient(135deg, #5b41ff, #6a5cff);
|
background: #ffffff;
|
||||||
color: #ffffff;
|
color: #2d3553;
|
||||||
box-shadow: 0 12px 28px rgba(95, 81, 255, 0.2);
|
box-shadow: 0 12px 28px rgba(60, 112, 245, 0.12);
|
||||||
}
|
}
|
||||||
.transcript-entry {
|
.transcript-entry {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 4px;
|
padding: 6px 8px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meetings-card-scroll .ant-list {
|
.meetings-card-scroll .ant-list {
|
||||||
|
|
@ -122,29 +122,33 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid #e6e6e6 !important;
|
border: none !important;
|
||||||
border-radius: 16px !important;
|
border-radius: 16px !important;
|
||||||
background: #fff !important;
|
background: #fff !important;
|
||||||
box-shadow: 0 2px 8px rgba(24, 39, 75, 0.04) !important;
|
box-shadow:
|
||||||
|
0 10px 26px rgba(24, 39, 75, 0.07),
|
||||||
|
0 2px 8px rgba(24, 39, 75, 0.05) !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
will-change: transform, box-shadow;
|
will-change: transform, box-shadow;
|
||||||
transition:
|
transition:
|
||||||
border-color 0.18s ease,
|
|
||||||
background 0.18s ease,
|
background 0.18s ease,
|
||||||
box-shadow 0.18s ease,
|
box-shadow 0.18s ease,
|
||||||
transform 0.18s ease;
|
transform 0.18s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meeting-card-v2.ant-card:hover {
|
.meeting-card-v2.ant-card:hover {
|
||||||
border-color: #8fb0fb !important;
|
background: linear-gradient(180deg, #ffffff 0%, #fbfdff 100%) !important;
|
||||||
background: #f9fafe !important;
|
box-shadow:
|
||||||
box-shadow: 0 8px 20px rgba(60, 112, 245, 0.14) !important;
|
0 16px 34px rgba(24, 39, 75, 0.12),
|
||||||
|
0 6px 14px rgba(60, 112, 245, 0.10) !important;
|
||||||
transform: translateY(-3px);
|
transform: translateY(-3px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.meeting-card-v2.ant-card:active {
|
.meeting-card-v2.ant-card:active {
|
||||||
box-shadow: 0 4px 12px rgba(60, 112, 245, 0.12) !important;
|
box-shadow:
|
||||||
|
0 8px 18px rgba(24, 39, 75, 0.10),
|
||||||
|
0 2px 8px rgba(60, 112, 245, 0.10) !important;
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -249,11 +249,13 @@ const TableStatusCell: React.FC<{ meeting: MeetingVO; progress: MeetingProgress
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMeetingTagList = (tags: unknown) => {
|
const getMeetingTagList = (tags: unknown) => {
|
||||||
|
const normalizeMeetingTag = (tag: unknown) => String(tag).replace(/^#+\s*/, '').trim();
|
||||||
|
|
||||||
if (Array.isArray(tags)) {
|
if (Array.isArray(tags)) {
|
||||||
return tags.map((tag) => String(tag).trim()).filter(Boolean);
|
return tags.map(normalizeMeetingTag).filter(Boolean);
|
||||||
}
|
}
|
||||||
if (typeof tags === "string") {
|
if (typeof tags === "string") {
|
||||||
return tags.split(",").map((tag) => tag.trim()).filter(Boolean);
|
return tags.split(",").map(normalizeMeetingTag).filter(Boolean);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
@ -382,7 +384,7 @@ const MeetingCardItem: React.FC<{
|
||||||
{tags.length > 0 ? (
|
{tags.length > 0 ? (
|
||||||
tags.map(tag => (
|
tags.map(tag => (
|
||||||
<span key={tag} className="meeting-card-v2__tag">
|
<span key={tag} className="meeting-card-v2__tag">
|
||||||
#{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -17,27 +17,6 @@
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-monitor-page__stats {
|
|
||||||
flex-shrink: 0;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-monitor-page__stat-card {
|
|
||||||
height: 100%;
|
|
||||||
border: 1px solid #e6e6e6;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-monitor-page__stat-card .ant-card-body {
|
|
||||||
padding: 18px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-monitor-page__stat-label {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-monitor-page__task-panel {
|
.dashboard-monitor-page__task-panel {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ import PageContainer from "@/components/shared/PageContainer";
|
||||||
import AppPagination from '@/components/shared/AppPagination';
|
import AppPagination from '@/components/shared/AppPagination';
|
||||||
import DataListPanel from "@/components/shared/DataListPanel";
|
import DataListPanel from "@/components/shared/DataListPanel";
|
||||||
import SectionCard from "@/components/shared/SectionCard";
|
import SectionCard from "@/components/shared/SectionCard";
|
||||||
import { Row, Col, Card, Statistic, List, Tag, Typography, Button, Space, Empty, Steps, Progress, Divider } from 'antd';
|
import SummaryStatCards from "@/components/shared/SummaryStatCards";
|
||||||
|
import { Row, Col, List, Tag, Typography, Button, Space, Empty, Steps, Progress, Divider } from 'antd';
|
||||||
import {
|
import {
|
||||||
HistoryOutlined,
|
HistoryOutlined,
|
||||||
CheckCircleOutlined,
|
CheckCircleOutlined,
|
||||||
|
|
@ -159,15 +160,16 @@ export const Dashboard: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const statCards = [
|
const statCards = [
|
||||||
{ label: '累计会议记录', value: stats?.totalMeetings, icon: <HistoryOutlined />, color: '#1890ff' },
|
{ key: 'totalMeetings', label: '累计会议记录', value: stats?.totalMeetings ?? 0, icon: <HistoryOutlined />, color: '#1890ff' },
|
||||||
{
|
{
|
||||||
|
key: 'processingTasks',
|
||||||
label: '当前分析中任务',
|
label: '当前分析中任务',
|
||||||
value: stats?.processingTasks,
|
value: stats?.processingTasks ?? 0,
|
||||||
icon: processingCount > 0 ? <LoadingOutlined spin /> : <ClockCircleOutlined />,
|
icon: processingCount > 0 ? <LoadingOutlined spin /> : <ClockCircleOutlined />,
|
||||||
color: '#faad14'
|
color: '#faad14'
|
||||||
},
|
},
|
||||||
{ label: '今日新增分析', value: stats?.todayNew, icon: <RiseOutlined />, color: '#52c41a' },
|
{ key: 'todayNew', label: '今日新增分析', value: stats?.todayNew ?? 0, icon: <RiseOutlined />, color: '#52c41a' },
|
||||||
{ label: 'AI 处理成功率', value: `${stats?.successRate || 100}%`, icon: <CheckCircleOutlined />, color: '#13c2c2' },
|
{ key: 'successRate', label: 'AI 处理成功率', value: `${stats?.successRate || 100}%`, icon: <CheckCircleOutlined />, color: '#13c2c2' },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -180,22 +182,7 @@ export const Dashboard: React.FC = () => {
|
||||||
description="系统运行概览与最近任务动态。"
|
description="系统运行概览与最近任务动态。"
|
||||||
contentClassName="dashboard-monitor-page__content"
|
contentClassName="dashboard-monitor-page__content"
|
||||||
>
|
>
|
||||||
<div className="dashboard-monitor-page__stats">
|
<SummaryStatCards items={statCards} ariaLabel="任务监控统计" />
|
||||||
<Row gutter={[16, 16]}>
|
|
||||||
{statCards.map((s, idx) => (
|
|
||||||
<Col xs={24} sm={12} xl={6} key={idx}>
|
|
||||||
<Card className="dashboard-monitor-page__stat-card" variant="borderless">
|
|
||||||
<Statistic
|
|
||||||
title={<Text type="secondary" className="dashboard-monitor-page__stat-label">{s.label}</Text>}
|
|
||||||
value={s.value || 0}
|
|
||||||
valueStyle={{ color: s.color, fontWeight: 700 }}
|
|
||||||
prefix={React.cloneElement(s.icon as React.ReactElement, { style: { marginRight: 8 } })}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DataListPanel
|
<DataListPanel
|
||||||
className="dashboard-monitor-page__task-panel"
|
className="dashboard-monitor-page__task-panel"
|
||||||
|
|
|
||||||
|
|
@ -12,37 +12,9 @@
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.devices-summary-chips {
|
.devices-page__content {
|
||||||
display: flex;
|
gap: 12px;
|
||||||
align-items: center;
|
padding: 8px;
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.devices-summary-chip {
|
|
||||||
height: 32px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 0 10px;
|
|
||||||
border: 1px solid #e6e6e6;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: #f9fafe;
|
|
||||||
color: #333;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 20px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.devices-summary-chip .anticon {
|
|
||||||
color: #3c70f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.devices-summary-chip strong {
|
|
||||||
color: #1677ff;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-icon-placeholder {
|
.device-icon-placeholder {
|
||||||
|
|
@ -115,9 +87,4 @@
|
||||||
.devices-page {
|
.devices-page {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.devices-summary-chips,
|
|
||||||
.devices-summary-chip {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import DataListPanel from "@/components/shared/DataListPanel";
|
||||||
import ListTable from "@/components/shared/ListTable/ListTable";
|
import ListTable from "@/components/shared/ListTable/ListTable";
|
||||||
import AppPagination from "@/components/shared/AppPagination";
|
import AppPagination from "@/components/shared/AppPagination";
|
||||||
import SectionCard from "@/components/shared/SectionCard";
|
import SectionCard from "@/components/shared/SectionCard";
|
||||||
|
import SummaryStatCards from "@/components/shared/SummaryStatCards";
|
||||||
import { useDict } from "@/hooks/useDict";
|
import { useDict } from "@/hooks/useDict";
|
||||||
import { usePermission } from "@/hooks/usePermission";
|
import { usePermission } from "@/hooks/usePermission";
|
||||||
import { getDefaultPageSize } from "@/utils/pagination";
|
import { getDefaultPageSize } from "@/utils/pagination";
|
||||||
|
|
@ -93,6 +94,12 @@ export default function Devices() {
|
||||||
return { total, online, enabled };
|
return { total, online, enabled };
|
||||||
}, [devices]);
|
}, [devices]);
|
||||||
|
|
||||||
|
const statCards = useMemo(() => [
|
||||||
|
{ key: "total", label: t("devicesExt.totalDevices"), value: stats.total, icon: <DesktopOutlined />, color: "#1890ff" },
|
||||||
|
{ key: "online", label: t("devicesExt.onlineDevices"), value: stats.online, icon: <ThunderboltOutlined />, color: "#faad14" },
|
||||||
|
{ key: "enabled", label: t("devicesExt.enabledDevices"), value: stats.enabled, icon: <CheckCircleOutlined />, color: "#1677ff" },
|
||||||
|
], [stats, t]);
|
||||||
|
|
||||||
const openEdit = (record: DeviceInfo) => {
|
const openEdit = (record: DeviceInfo) => {
|
||||||
setEditing(record);
|
setEditing(record);
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
|
|
@ -305,27 +312,10 @@ export default function Devices() {
|
||||||
<SectionCard
|
<SectionCard
|
||||||
title={t("devices.title")}
|
title={t("devices.title")}
|
||||||
description={t("devices.subtitle")}
|
description={t("devices.subtitle")}
|
||||||
|
contentClassName="devices-page__content"
|
||||||
>
|
>
|
||||||
|
<SummaryStatCards items={statCards} ariaLabel={t("devices.title")} />
|
||||||
<DataListPanel
|
<DataListPanel
|
||||||
leftActions={
|
|
||||||
<div className="devices-summary-chips" aria-label={t("devices.title")}>
|
|
||||||
<span className="devices-summary-chip">
|
|
||||||
<DesktopOutlined aria-hidden="true" />
|
|
||||||
<span>{t("devicesExt.totalDevices")}</span>
|
|
||||||
<strong className="tabular-nums">{stats.total}</strong>
|
|
||||||
</span>
|
|
||||||
<span className="devices-summary-chip">
|
|
||||||
<ThunderboltOutlined aria-hidden="true" />
|
|
||||||
<span>{t("devicesExt.onlineDevices")}</span>
|
|
||||||
<strong className="tabular-nums">{stats.online}</strong>
|
|
||||||
</span>
|
|
||||||
<span className="devices-summary-chip">
|
|
||||||
<CheckCircleOutlined aria-hidden="true" />
|
|
||||||
<span>{t("devicesExt.enabledDevices")}</span>
|
|
||||||
<strong className="tabular-nums">{stats.enabled}</strong>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
rightActions={
|
rightActions={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Input
|
<Input
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 8px 4px 0;
|
padding: 8px 8px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tenants-grid {
|
.tenants-grid {
|
||||||
|
|
@ -53,29 +53,33 @@
|
||||||
min-height: 230px;
|
min-height: 230px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border: 1px solid #e6e6e6 !important;
|
border: none !important;
|
||||||
border-radius: 16px !important;
|
border-radius: 16px !important;
|
||||||
background: #fff !important;
|
background: #fff !important;
|
||||||
box-shadow: 0 2px 8px rgba(24, 39, 75, 0.04) !important;
|
box-shadow:
|
||||||
|
0 10px 26px rgba(24, 39, 75, 0.07),
|
||||||
|
0 2px 8px rgba(24, 39, 75, 0.05) !important;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
will-change: transform, box-shadow;
|
will-change: transform, box-shadow;
|
||||||
transition:
|
transition:
|
||||||
border-color 0.18s ease,
|
|
||||||
background 0.18s ease,
|
background 0.18s ease,
|
||||||
box-shadow 0.18s ease,
|
box-shadow 0.18s ease,
|
||||||
transform 0.18s ease;
|
transform 0.18s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tenant-card:hover {
|
.tenant-card:hover {
|
||||||
border-color: #8fb0fb !important;
|
background: linear-gradient(180deg, #ffffff 0%, #fbfdff 100%) !important;
|
||||||
background: #f9fafe !important;
|
box-shadow:
|
||||||
box-shadow: 0 8px 20px rgba(60, 112, 245, 0.14) !important;
|
0 16px 34px rgba(24, 39, 75, 0.12),
|
||||||
|
0 6px 14px rgba(60, 112, 245, 0.10) !important;
|
||||||
transform: translateY(-3px);
|
transform: translateY(-3px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tenant-card:active {
|
.tenant-card:active {
|
||||||
box-shadow: 0 4px 12px rgba(60, 112, 245, 0.12) !important;
|
box-shadow:
|
||||||
|
0 8px 18px rgba(24, 39, 75, 0.10),
|
||||||
|
0 2px 8px rgba(60, 112, 245, 0.10) !important;
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,18 @@
|
||||||
min-height: 0; /* Important for flex child scroll */
|
min-height: 0; /* Important for flex child scroll */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dict-type-search-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dict-type-search-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.full-height {
|
.full-height {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,8 +183,8 @@ export default function Dictionaries() {
|
||||||
}} extra={can("sys_dict:type:create") &&
|
}} extra={can("sys_dict:type:create") &&
|
||||||
<Button type="primary" size="small" icon={<PlusOutlined aria-hidden="true"/>}
|
<Button type="primary" size="small" icon={<PlusOutlined aria-hidden="true"/>}
|
||||||
onClick={handleAddType}>{t("common.create")}</Button>}>
|
onClick={handleAddType}>{t("common.create")}</Button>}>
|
||||||
<div style={{ marginBottom: 12 }} className="flex-shrink-0">
|
<div className="dict-type-search-row flex-shrink-0">
|
||||||
<Space.Compact style={{ width: "100%" }}>
|
<Space.Compact className="dict-type-search-input">
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("dictsExt.searchTypes")}
|
placeholder={t("dictsExt.searchTypes")}
|
||||||
allowClear
|
allowClear
|
||||||
|
|
@ -193,8 +193,8 @@ export default function Dictionaries() {
|
||||||
onPressEnter={() => handleTypeSearch(typeKeyword)}
|
onPressEnter={() => handleTypeSearch(typeKeyword)}
|
||||||
/>
|
/>
|
||||||
<Button type="primary" icon={<SearchOutlined aria-hidden="true" />} onClick={() => handleTypeSearch(typeKeyword)}>{t("common.search")}</Button>
|
<Button type="primary" icon={<SearchOutlined aria-hidden="true" />} onClick={() => handleTypeSearch(typeKeyword)}>{t("common.search")}</Button>
|
||||||
<Button onClick={handleTypeReset}>{t("common.reset")}</Button>
|
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
|
<Button onClick={handleTypeReset}>{t("common.reset")}</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="dict-type-table-wrap">
|
<div className="dict-type-table-wrap">
|
||||||
<Table
|
<Table
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue