feat: 添加分页和优化会议任务处理逻辑
- 在前端 `Dashboard` 页面中添加分页功能 - 优化 `AiTaskServiceImpl` 中的 ASR 任务处理逻辑,支持任务恢复和失败处理 - 更新相关服务和组件以支持新的分页和任务处理逻辑dev_na
parent
f7480df565
commit
7d08234919
|
|
@ -214,26 +214,18 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
? String.valueOf(taskRecord.getResponseData().getOrDefault("task_id", ""))
|
? String.valueOf(taskRecord.getResponseData().getOrDefault("task_id", ""))
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
if (taskId == null || taskId.isBlank()) {
|
if (taskId != null && !taskId.isBlank()) {
|
||||||
updateProgress(meeting.getId(), 5, "正在提交识别请求...", 0);
|
|
||||||
Map<String, Object> req = buildAsrRequest(meeting, taskRecord, asrModel);
|
|
||||||
taskRecord.setRequestData(req);
|
|
||||||
this.updateById(taskRecord);
|
|
||||||
|
|
||||||
String respBody = postJson(submitUrl, req, asrModel.getApiKey());
|
|
||||||
JsonNode submitNode = objectMapper.readTree(respBody);
|
|
||||||
if (submitNode.path("code").asInt() != 0) {
|
|
||||||
updateAiTaskFail(taskRecord, "提交失败: " + respBody);
|
|
||||||
throw new RuntimeException("ASR引擎拒绝请求: " + submitNode.path("msg").asText());
|
|
||||||
}
|
|
||||||
taskId = submitNode.path("data").path("task_id").asText();
|
|
||||||
taskRecord.setResponseData(Map.of("task_id", taskId));
|
|
||||||
this.updateById(taskRecord);
|
|
||||||
} else {
|
|
||||||
updateProgress(meeting.getId(), 5, "Resuming ASR polling...", 0);
|
updateProgress(meeting.getId(), 5, "Resuming ASR polling...", 0);
|
||||||
|
if (!canResumeAsrTask(asrModel, meeting.getId(), taskId)) {
|
||||||
|
clearAsrTaskId(taskRecord);
|
||||||
|
taskId = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskId == null || taskId.isBlank()) {
|
||||||
|
taskId = submitAsrTask(meeting, taskRecord, asrModel, submitUrl);
|
||||||
}
|
}
|
||||||
this.updateById(taskRecord);
|
this.updateById(taskRecord);
|
||||||
|
|
||||||
String queryUrl = appendPath(asrModel.getBaseUrl(), "api/v1/asr/transcriptions/" + taskId);
|
String queryUrl = appendPath(asrModel.getBaseUrl(), "api/v1/asr/transcriptions/" + taskId);
|
||||||
|
|
||||||
// 轮询逻辑(带防卡死防护)
|
// 轮询逻辑(带防卡死防护)
|
||||||
|
|
@ -245,6 +237,11 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
Thread.sleep(2000);
|
Thread.sleep(2000);
|
||||||
String queryResp = get(queryUrl, asrModel.getApiKey());
|
String queryResp = get(queryUrl, asrModel.getApiKey());
|
||||||
JsonNode statusNode = objectMapper.readTree(queryResp);
|
JsonNode statusNode = objectMapper.readTree(queryResp);
|
||||||
|
int code = statusNode.path("code").asInt(500);
|
||||||
|
if (code!=0){
|
||||||
|
updateAiTaskFail(taskRecord, "ASR 引擎返回失败:" + queryResp);
|
||||||
|
throw new RuntimeException("ASR引擎处理失败: " + statusNode.get("message").asText());
|
||||||
|
}
|
||||||
JsonNode data = statusNode.path("data");
|
JsonNode data = statusNode.path("data");
|
||||||
String status = data.path("status").asText();
|
String status = data.path("status").asText();
|
||||||
|
|
||||||
|
|
@ -325,6 +322,58 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean canResumeAsrTask(AiModelVO asrModel, Long meetingId, String taskId) {
|
||||||
|
String queryUrl = appendPath(asrModel.getBaseUrl(), "api/v1/asr/transcriptions/" + taskId);
|
||||||
|
try {
|
||||||
|
String queryResp = get(queryUrl, asrModel.getApiKey());
|
||||||
|
JsonNode statusNode = objectMapper.readTree(queryResp);
|
||||||
|
int code = statusNode.path("code").asInt(500);
|
||||||
|
if (code != 0) {
|
||||||
|
log.warn("ASR task {} progress fetch failed for meeting {}, will resubmit task. response={}",
|
||||||
|
taskId, meetingId, queryResp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String status = statusNode.path("data").path("status").asText();
|
||||||
|
if ("failed".equalsIgnoreCase(status)) {
|
||||||
|
log.warn("ASR task {} already failed for meeting {}, will resubmit task.", taskId, meetingId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("ASR task {} progress fetch threw exception for meeting {}, will resubmit task.",
|
||||||
|
taskId, meetingId, ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearAsrTaskId(AiTask taskRecord) {
|
||||||
|
if (taskRecord.getResponseData() == null || taskRecord.getResponseData().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, Object> responseData = new HashMap<>(taskRecord.getResponseData());
|
||||||
|
responseData.remove("task_id");
|
||||||
|
taskRecord.setResponseData(responseData.isEmpty() ? null : responseData);
|
||||||
|
this.updateById(taskRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String submitAsrTask(Meeting meeting, AiTask taskRecord, AiModelVO asrModel, String submitUrl) throws Exception {
|
||||||
|
updateProgress(meeting.getId(), 5, "重新提交任务...", 0);
|
||||||
|
Map<String, Object> req = buildAsrRequest(meeting, taskRecord, asrModel);
|
||||||
|
taskRecord.setRequestData(req);
|
||||||
|
this.updateById(taskRecord);
|
||||||
|
|
||||||
|
String respBody = postJson(submitUrl, req, asrModel.getApiKey());
|
||||||
|
JsonNode submitNode = objectMapper.readTree(respBody);
|
||||||
|
if (submitNode.path("code").asInt() != 0) {
|
||||||
|
updateAiTaskFail(taskRecord, "ASR识别失败: " + respBody);
|
||||||
|
throw new RuntimeException("ASR识别失败: " + submitNode.path("msg").asText());
|
||||||
|
}
|
||||||
|
String taskId = submitNode.path("data").path("task_id").asText();
|
||||||
|
taskRecord.setResponseData(Map.of("task_id", taskId));
|
||||||
|
this.updateById(taskRecord);
|
||||||
|
return taskId;
|
||||||
|
}
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
protected String saveTranscripts(Meeting meeting, JsonNode resultNode) {
|
protected String saveTranscripts(Meeting meeting, JsonNode resultNode) {
|
||||||
// 关键:入库前清理旧记录,防止恢复任务导致数据重复
|
// 关键:入库前清理旧记录,防止恢复任务导致数据重复
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import PageContainer from "@/components/shared/PageContainer";
|
import PageContainer from "@/components/shared/PageContainer";
|
||||||
|
import AppPagination from '@/components/shared/AppPagination';
|
||||||
import { Row, Col, Card, Statistic, List, Tag, Typography, Button, Space, Empty, Steps, Progress, Divider } from 'antd';
|
import { Row, Col, Card, Statistic, List, Tag, Typography, Button, Space, Empty, Steps, Progress, Divider } from 'antd';
|
||||||
import {
|
import {
|
||||||
HistoryOutlined,
|
HistoryOutlined,
|
||||||
|
|
@ -16,8 +17,8 @@ import {
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { getDashboardStats, getRecentTasks, DashboardStats } from '@/api/business/dashboard';
|
import { getDashboardStats, DashboardStats } from '@/api/business/dashboard';
|
||||||
import { MeetingVO, getMeetingProgress, MeetingProgress } from '@/api/business/meeting';
|
import { MeetingVO, getMeetingPage, getMeetingProgress, MeetingProgress } from '@/api/business/meeting';
|
||||||
|
|
||||||
const { Title, Text } = Typography;
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
|
|
@ -33,12 +34,12 @@ const MeetingProgressDisplay: React.FC<{ meeting: MeetingVO }> = ({ meeting }) =
|
||||||
if (res.data?.data) {
|
if (res.data?.data) {
|
||||||
setProgress(res.data.data);
|
setProgress(res.data.data);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchProgress();
|
void fetchProgress();
|
||||||
const timer = setInterval(fetchProgress, 3000);
|
const timer = setInterval(fetchProgress, 3000);
|
||||||
return () => clearInterval(timer);
|
return () => clearInterval(timer);
|
||||||
}, [meeting.id, meeting.status]);
|
}, [meeting.id, meeting.status]);
|
||||||
|
|
@ -50,7 +51,7 @@ const MeetingProgressDisplay: React.FC<{ meeting: MeetingVO }> = ({ meeting }) =
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginTop: 12, padding: '12px 16px', background: 'var(--app-bg-surface-soft)', borderRadius: 8, border: '1px solid var(--app-border-color)' }}>
|
<div style={{ marginTop: 12, padding: '12px 16px', background: 'var(--app-bg-surface-soft)', borderRadius: 8, border: '1px solid var(--app-border-color)' }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, marginBottom: 6 }}>
|
||||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
<LoadingOutlined style={{ marginRight: 6, color: '#1890ff' }} spin={!isError} />
|
<LoadingOutlined style={{ marginRight: 6, color: '#1890ff' }} spin={!isError} />
|
||||||
{progress?.message || '准备分析中...'}
|
{progress?.message || '准备分析中...'}
|
||||||
|
|
@ -73,33 +74,62 @@ export const Dashboard: React.FC = () => {
|
||||||
const [stats, setStats] = useState<DashboardStats | null>(null);
|
const [stats, setStats] = useState<DashboardStats | null>(null);
|
||||||
const [recentTasks, setRecentTasks] = useState<MeetingVO[]>([]);
|
const [recentTasks, setRecentTasks] = useState<MeetingVO[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });
|
||||||
|
|
||||||
const processingCount = Number(stats?.processingTasks || 0);
|
const processingCount = Number(stats?.processingTasks || 0);
|
||||||
const dashboardLoading = loading && processingCount > 0;
|
const dashboardLoading = loading;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchDashboardData();
|
void fetchDashboardData();
|
||||||
const timer = setInterval(fetchDashboardData, 5000);
|
}, [pagination.current, pagination.pageSize]);
|
||||||
return () => clearInterval(timer);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const fetchDashboardData = async () => {
|
useEffect(() => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
void fetchDashboardData(true);
|
||||||
|
}, 5000);
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, [pagination.current, pagination.pageSize]);
|
||||||
|
|
||||||
|
const fetchDashboardData = async (silent = false) => {
|
||||||
|
if (!silent) {
|
||||||
|
setLoading(true);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const [statsRes, tasksRes] = await Promise.all([getDashboardStats(), getRecentTasks()]);
|
const [statsRes, tasksRes] = await Promise.all([
|
||||||
|
getDashboardStats(),
|
||||||
|
getMeetingPage({
|
||||||
|
current: pagination.current,
|
||||||
|
size: pagination.pageSize,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
setStats(statsRes.data.data);
|
setStats(statsRes.data.data);
|
||||||
setRecentTasks(tasksRes.data.data || []);
|
setRecentTasks(tasksRes.data?.data?.records || []);
|
||||||
|
setPagination((prev) => ({
|
||||||
|
...prev,
|
||||||
|
total: tasksRes.data?.data?.total || 0,
|
||||||
|
}));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Dashboard data load failed', err);
|
console.error('Dashboard data load failed', err);
|
||||||
} finally {
|
} finally {
|
||||||
|
if (!silent) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = (page: number, pageSize: number) => {
|
||||||
|
setPagination((prev) => ({
|
||||||
|
...prev,
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderTaskProgress = (item: MeetingVO) => {
|
const renderTaskProgress = (item: MeetingVO) => {
|
||||||
const currentStep = item.status === 4 ? 0 : (item.status === 3 ? 2 : item.status);
|
const currentStep = item.status === 4 ? 0 : (item.status === 3 ? 2 : item.status);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '100%', maxWidth: 450 }}>
|
<div style={{ width: '100%', maxWidth: '100%' }}>
|
||||||
<Steps
|
<Steps
|
||||||
size="small"
|
size="small"
|
||||||
current={currentStep}
|
current={currentStep}
|
||||||
|
|
@ -139,12 +169,13 @@ export const Dashboard: React.FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
title="仪表板"
|
title="仪表盘"
|
||||||
subtitle="系统运行概览与最近任务动态"
|
subtitle="系统运行概览与最近任务动态"
|
||||||
|
style={{ overflow: 'hidden' }}
|
||||||
>
|
>
|
||||||
<Row gutter={24} style={{ marginBottom: 24 }}>
|
<Row gutter={[16, 16]} style={{ marginBottom: 16, flexShrink: 0 }}>
|
||||||
{statCards.map((s, idx) => (
|
{statCards.map((s, idx) => (
|
||||||
<Col span={6} key={idx}>
|
<Col xs={24} sm={12} xl={6} key={idx}>
|
||||||
<Card variant="borderless" style={{ borderRadius: 16, boxShadow: 'var(--app-shadow)', background: 'var(--app-bg-card)', border: '1px solid var(--app-border-color)', backdropFilter: 'blur(16px)' }}>
|
<Card variant="borderless" style={{ borderRadius: 16, boxShadow: 'var(--app-shadow)', background: 'var(--app-bg-card)', border: '1px solid var(--app-border-color)', backdropFilter: 'blur(16px)' }}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={<Text type="secondary" style={{ fontSize: 13 }}>{s.label}</Text>}
|
title={<Text type="secondary" style={{ fontSize: 13 }}>{s.label}</Text>}
|
||||||
|
|
@ -158,44 +189,48 @@ export const Dashboard: React.FC = () => {
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
|
className="dashboard-task-card"
|
||||||
title={
|
title={
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 12 }}>
|
||||||
<Space><ClockCircleOutlined /> 最近任务动态</Space>
|
<Space><ClockCircleOutlined /> 最近任务动态</Space>
|
||||||
<Button type="link" onClick={() => navigate('/meetings')}>查看历史记录</Button>
|
<Button type="link" onClick={() => navigate('/meetings')}>查看历史记录</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
style={{ borderRadius: 16, boxShadow: 'var(--app-shadow)', background: 'var(--app-bg-card)', border: '1px solid var(--app-border-color)', backdropFilter: 'blur(16px)' }}
|
style={{ flex: 1, minHeight: 0, borderRadius: 16, boxShadow: 'var(--app-shadow)', background: 'var(--app-bg-card)', border: '1px solid var(--app-border-color)', backdropFilter: 'blur(16px)', overflow: 'hidden' }}
|
||||||
|
styles={{ body: { display: 'flex', flexDirection: 'column', gap: 16, flex: 1, minHeight: 0, overflow: 'hidden',height:'90%' } }}
|
||||||
>
|
>
|
||||||
|
<div className="dashboard-task-list">
|
||||||
<List
|
<List
|
||||||
loading={dashboardLoading}
|
loading={dashboardLoading}
|
||||||
dataSource={recentTasks}
|
dataSource={recentTasks}
|
||||||
renderItem={(item) => (
|
renderItem={(item) => (
|
||||||
<List.Item style={{ padding: '24px 0', borderBottom: '1px solid #f0f2f5' }}>
|
<List.Item style={{ padding: '24px 0', borderBottom: '1px solid #f0f2f5' }}>
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
<Row gutter={32} align="middle">
|
<Row gutter={[24, 16]} align="middle">
|
||||||
<Col span={8}>
|
<Col xs={24} xl={8}>
|
||||||
<Space direction="vertical" size={4}>
|
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
||||||
<Title level={5} style={{ margin: 0, cursor: 'pointer' }} onClick={() => navigate(`/meetings/${item.id}`)}>
|
<Title level={5} style={{ margin: 0, cursor: 'pointer', wordBreak: 'break-word' }} onClick={() => navigate(`/meetings/${item.id}`)}>
|
||||||
{item.title}
|
{item.title}
|
||||||
</Title>
|
</Title>
|
||||||
<Space size={12} split={<Divider type="vertical" style={{ margin: 0 }} />}>
|
<Space size={12} wrap split={<Divider type="vertical" style={{ margin: 0 }} />}>
|
||||||
<Text type="secondary"><CalendarOutlined /> {dayjs(item.meetingTime).format('MM-DD HH:mm')}</Text>
|
<Text type="secondary"><CalendarOutlined /> {dayjs(item.meetingTime).format('MM-DD HH:mm')}</Text>
|
||||||
<Text type="secondary"><TeamOutlined /> {item.participants || item.creatorName || '未指定'}</Text>
|
<Text type="secondary"><TeamOutlined /> {item.participants || item.creatorName || '未指定'}</Text>
|
||||||
</Space>
|
</Space>
|
||||||
<div style={{ marginTop: 8 }}>
|
<div style={{ marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
||||||
{item.tags?.split(',').filter(Boolean).map((t) => (
|
{item.tags?.split(',').filter(Boolean).map((t) => (
|
||||||
<Tag key={t} style={{ border: '1px solid var(--app-border-color)', background: 'color-mix(in srgb, var(--app-primary-color) 12%, var(--app-bg-surface-strong))', color: 'var(--app-text-main)', borderRadius: 4, fontSize: 11 }}>{t}</Tag>
|
<Tag key={t} style={{ marginInlineEnd: 0, border: '1px solid var(--app-border-color)', background: 'color-mix(in srgb, var(--app-primary-color) 12%, var(--app-bg-surface-strong))', color: 'var(--app-text-main)', borderRadius: 4, fontSize: 11 }}>{t}</Tag>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Space>
|
</Space>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col span={12}>
|
<Col xs={24} xl={12}>
|
||||||
{renderTaskProgress(item)}
|
{renderTaskProgress(item)}
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col span={4} style={{ textAlign: 'right' }}>
|
<Col xs={24} xl={4}>
|
||||||
|
<div className="dashboard-task-action">
|
||||||
<Button
|
<Button
|
||||||
type={item.status === 3 ? 'primary' : 'default'}
|
type={item.status === 3 ? 'primary' : 'default'}
|
||||||
ghost={item.status === 3}
|
ghost={item.status === 3}
|
||||||
|
|
@ -204,6 +239,7 @@ export const Dashboard: React.FC = () => {
|
||||||
>
|
>
|
||||||
{item.status === 3 ? '查看纪要' : '监控详情'}
|
{item.status === 3 ? '查看纪要' : '监控详情'}
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
|
@ -213,11 +249,44 @@ export const Dashboard: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
locale={{ emptyText: <Empty description="暂无近期分析任务" /> }}
|
locale={{ emptyText: <Empty description="暂无近期分析任务" /> }}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<AppPagination
|
||||||
|
current={pagination.current}
|
||||||
|
pageSize={pagination.pageSize}
|
||||||
|
total={pagination.total}
|
||||||
|
onChange={handlePageChange}
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<style>{`
|
<style>{`
|
||||||
|
.dashboard-task-card .ant-card-head,
|
||||||
|
.dashboard-task-card .ant-card-body,
|
||||||
|
.dashboard-task-card .ant-list,
|
||||||
|
.dashboard-task-card .ant-list-items,
|
||||||
|
.dashboard-task-card .ant-list-item {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.dashboard-task-list {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding-right: 4px;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
|
.dashboard-task-action {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
.ant-steps-item-title { font-size: 13px !important; font-weight: 600 !important; }
|
.ant-steps-item-title { font-size: 13px !important; font-weight: 600 !important; }
|
||||||
.ant-steps-item-description { font-size: 11px !important; }
|
.ant-steps-item-description { font-size: 11px !important; }
|
||||||
|
@media (max-width: 1199px) {
|
||||||
|
.dashboard-task-action {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue