| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691 |
- <!-- 待办预警 -->
- <template>
- <div class="app-container todo-warning-page" v-loading="loading">
- <el-card shadow="never" class="page-card header-card">
- <div class="page-header">
- <div>
- <div class="page-title">待办预警</div>
- <div class="page-subtitle">预警任务与督办通知</div>
- </div>
- <el-button type="primary" icon="Refresh" @click="loadCurrentTabData">刷新</el-button>
- </div>
- </el-card>
- <div class="summary-grid">
- <el-card shadow="hover" class="summary-card danger">
- <div class="summary-label">我的待办</div>
- <div class="summary-value">{{ todoCount }}</div>
- <div class="summary-desc">当前用户待处理预警数量</div>
- </el-card>
- <el-card shadow="hover" class="summary-card success">
- <div class="summary-label">我的已办</div>
- <div class="summary-value">{{ doneCount }}</div>
- <div class="summary-desc">当前用户已处理完成预警数量</div>
- </el-card>
- <el-card shadow="hover" class="summary-card primary">
- <div class="summary-label">当前视图</div>
- <div class="summary-value">{{ pagedList.length }}</div>
- <div class="summary-desc">本页筛选后可查看记录数</div>
- </el-card>
- </div>
- <el-card shadow="never" class="page-card">
- <el-form :model="queryParams" :inline="true">
- <el-form-item label="预警编号">
- <el-input
- v-model="queryParams.warningNo"
- placeholder="请输入预警编号"
- clearable
- style="width: 220px"
- @keyup.enter="handleQuery"
- />
- </el-form-item>
- <el-form-item label="预警专项">
- <el-input v-model="queryParams.warningSpecial" placeholder="请输入预警专项" clearable style="width: 200px" />
- </el-form-item>
- <el-form-item label="预警类型">
- <el-select v-model="queryParams.warningType" clearable placeholder="请选择预警类型" style="width: 180px">
- <el-option
- v-for="item in warningTypeOptions"
- :key="item.dictValue"
- :label="item.dictLabel"
- :value="item.dictValue"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="预警级别">
- <el-select v-model="queryParams.warningLevel" clearable placeholder="请选择预警级别" style="width: 180px">
- <el-option
- v-for="item in warningLevelOptions"
- :key="item.dictValue"
- :label="item.dictLabel"
- :value="item.dictValue"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="处置人">
- <el-input v-model="queryParams.handler" placeholder="请输入处置人" clearable style="width: 180px" />
- </el-form-item>
- <el-form-item label="发布人">
- <el-input v-model="queryParams.publisher" placeholder="请输入发布人" clearable style="width: 180px" />
- </el-form-item>
- <el-form-item label="报警编号">
- <el-input v-model="queryParams.alarmCode" placeholder="请输入报警编号" clearable style="width: 180px" />
- </el-form-item>
- <el-form-item label="权属单位">
- <el-input v-model="queryParams.ownershipUnit" placeholder="请输入权属单位" clearable style="width: 180px" />
- </el-form-item>
- <el-form-item label="发布时间">
- <el-date-picker
- v-model="queryParams.dateRange"
- type="datetimerange"
- value-format="YYYY-MM-DD HH:mm:ss"
- range-separator="至"
- start-placeholder="开始时间"
- end-placeholder="结束时间"
- style="width: 360px"
- />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
- <el-button icon="Refresh" @click="resetQuery">重置</el-button>
- </el-form-item>
- </el-form>
- </el-card>
- <el-card shadow="never" class="page-card">
- <el-tabs v-model="activeTab" @tab-change="handleTabChange">
- <el-tab-pane :label="`我的待办 (${todoCount})`" name="TODO" />
- <el-tab-pane :label="`我的已办 (${doneCount})`" name="DONE" />
- <el-tab-pane :label="`系统通知 (${noticeCount})`" name="NOTICE" />
- </el-tabs>
- <el-table :data="pagedList" @row-click="handleViewDetail">
- <el-table-column label="序号" width="70" align="center">
- <template #default="scope">
- {{ (pagination.pageNum - 1) * pagination.pageSize + scope.$index + 1 }}
- </template>
- </el-table-column>
- <el-table-column v-if="activeTab === 'NOTICE'" label="督办通知" prop="taskName" min-width="250" show-overflow-tooltip />
- <el-table-column v-if="activeTab === 'NOTICE'" label="阅读状态" width="90">
- <template #default="scope">{{ scope.row.readStatus === 'READ' ? '已读' : '未读' }}</template>
- </el-table-column>
- <el-table-column label="当前环节截止" min-width="165">
- <template #default="{ row }">{{ formatDateTime(row.stage_deadline || row.stageDeadline) }}</template>
- </el-table-column>
- <el-table-column label="预警编号" prop="warningNo" width="170" />
- <el-table-column label="预警专项" prop="warningSpecial" width="140" show-overflow-tooltip />
- <el-table-column label="预警类型" prop="warningTypeText" width="120" />
- <el-table-column label="处置人" prop="handler" width="120" />
- <el-table-column label="发布人" prop="publisher" width="120" />
- <el-table-column label="报警编号" width="160">
- <template #default="scope">
- {{ scope.row.alarmCodeList.length ? scope.row.alarmCodeList.join(",") : "-" }}
- </template>
- </el-table-column>
- <el-table-column label="权属单位" prop="ownershipUnit" width="160" show-overflow-tooltip />
- <el-table-column label="预警级别" width="110" align="center">
- <template #default="scope">
- <el-tag :type="getLevelTagType(scope.row.warningLevel)">
- {{ scope.row.warningLevelText || "-" }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="发布时间" width="180">
- <template #default="scope">{{ formatDateTime(scope.row.publishTime) }}</template>
- </el-table-column>
- <el-table-column label="状态" width="100" align="center">
- <template #default="scope">
- <el-tag :type="getStatusTagType(scope.row.status)">
- {{ getStatusText(scope.row.status) }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" fixed="right" width="300" align="center">
- <template #default="scope">
- <el-button v-hasPermi="['warning:warning:query']" link type="primary" @click.stop="handleViewDetail(scope.row)">查看详情</el-button>
- <template v-if="activeTab === 'TODO'">
- <WarningWorkflowActions :row="scope.row" @changed="refreshLists" />
- </template>
- </template>
- </el-table-column>
- </el-table>
- <pagination
- v-show="currentTotal > 0"
- :total="currentTotal"
- v-model:page="pagination.pageNum"
- v-model:limit="pagination.pageSize"
- @pagination="handlePageChange"
- />
- </el-card>
- <el-drawer v-model="detailVisible" title="预警详情" size="760px" append-to-body direction="rtl" class="detail-drawer">
- <el-alert v-if="activeTab === 'NOTICE' && selectedNotice" :title="selectedNotice" type="info" :closable="false" style="margin-bottom: 16px" />
- <template v-if="detailData.basicInfo">
- <WarningItemSummary :warning="detailData.basicInfo" />
- <el-descriptions :column="2" border>
- <el-descriptions-item label="预警编号">{{ detailBasic.warningNo || "-" }}</el-descriptions-item>
- <el-descriptions-item label="预警专项">{{ detailBasic.warningSpecial || "-" }}</el-descriptions-item>
- <el-descriptions-item label="预警类型">{{ detailBasic.warningTypeText || "-" }}</el-descriptions-item>
- <el-descriptions-item label="预警级别">{{ detailBasic.warningLevelText || "-" }}</el-descriptions-item>
- <el-descriptions-item label="处置人">{{ detailBasic.handler || "-" }}</el-descriptions-item>
- <el-descriptions-item label="发布人">{{ detailBasic.publisher || "-" }}</el-descriptions-item>
- <el-descriptions-item label="权属单位">{{ detailBasic.ownershipUnit || "-" }}</el-descriptions-item>
- <el-descriptions-item label="发布时间">{{ formatDateTime(detailBasic.publishTime) }}</el-descriptions-item>
- <el-descriptions-item label="预警内容" :span="2"><span class="warning-content">{{ detailBasic.warningContent || "-" }}</span></el-descriptions-item>
- <el-descriptions-item label="备注" :span="2">{{ detailBasic.remark || "-" }}</el-descriptions-item>
- </el-descriptions>
- <el-divider content-position="left">关联报警</el-divider>
- <el-table :data="detailData.alarmList || []" border size="small">
- <el-table-column label="报警编号" prop="warning_code" min-width="160" />
- <el-table-column label="报警类型" prop="warning_type" min-width="120" />
- <el-table-column label="设备编码" prop="device_code" min-width="160" />
- <el-table-column label="实际值" prop="actual_value" min-width="100" />
- <el-table-column label="报警时间" prop="createTime" min-width="180" />
- </el-table>
- <el-divider content-position="left">处置记录</el-divider>
- <el-timeline v-if="(detailData.disposalHistory || []).length">
- <el-timeline-item
- v-for="(item, index) in detailData.disposalHistory"
- :key="index"
- :timestamp="formatDateTime(item.createTime || item.create_time || item.disposalTime || item.disposal_time)"
- >
- {{ item.disposalTypeName || item.disposal_type_name || item.disposalType || item.disposal_type || "处置记录" }}
- <span v-if="item.disposalContent || item.disposal_content || item.content">:{{ item.disposalContent || item.disposal_content || item.content }}</span>
- <span v-if="item.disposalUser || item.disposal_user">({{ item.disposalUser || item.disposal_user }})</span>
- <WarningDisposalReasons :record="item" />
- <WarningAttachments :attachments="attachmentsForDisposal(item, detailData.attachmentList)" />
- </el-timeline-item>
- </el-timeline>
- <el-empty v-else description="暂无处置记录" />
- <template v-if="unlinkedWarningAttachments(detailData.disposalHistory, detailData.attachmentList).length">
- <el-divider content-position="left">其他预警附件</el-divider>
- <WarningAttachments :attachments="unlinkedWarningAttachments(detailData.disposalHistory, detailData.attachmentList)" />
- </template>
- </template>
- </el-drawer>
- <!-- 预警解除对话框 -->
- <el-dialog v-model="resolveDialog.visible" title="解除预警" width="500px" append-to-body>
- <el-form label-width="100px">
- <el-form-item label="预警编号">
- <el-input :model-value="resolveDialog.row?.warningNo || '-'" disabled />
- </el-form-item>
- <el-form-item label="处置说明" required>
- <el-input
- v-model="resolveDialog.disposalContent"
- type="textarea"
- :rows="4"
- maxlength="500"
- show-word-limit
- placeholder="请输入处置说明"
- />
- </el-form-item>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button @click="resolveDialog.visible = false">取 消</el-button>
- <el-button type="primary" :loading="actionLoading" @click="confirmResolve">确 定</el-button>
- </div>
- </template>
- </el-dialog>
- <!-- 退回重办对话框 -->
- <el-dialog v-model="returnDialog.visible" title="退回重办" width="500px" append-to-body>
- <el-form label-width="100px">
- <el-form-item label="预警编号">
- <el-input :model-value="returnDialog.row?.warningNo || '-'" disabled />
- </el-form-item>
- <el-form-item label="退回说明" required>
- <el-input
- v-model="returnDialog.disposalContent"
- type="textarea"
- :rows="4"
- maxlength="500"
- show-word-limit
- placeholder="请输入退回说明"
- />
- </el-form-item>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button @click="returnDialog.visible = false">取 消</el-button>
- <el-button type="primary" :loading="actionLoading" @click="confirmReturn">确 定</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup>
- import { useWarningStatuses } from '@/views/subSystem/lifeCompany/warningWorkflow'
- const { warningStatusOptions, databaseStatusLabels } = useWarningStatuses()
- import WarningItemSummary from '../components/WarningItemSummary.vue';
- import WarningDisposalReasons from '../components/WarningDisposalReasons.vue';
- import WarningAttachments from "../components/WarningAttachments.vue";
- import { attachmentsForDisposal, unlinkedWarningAttachments } from "../warningAttachments";
- import { computed, onMounted, reactive, ref } from "vue";
- import auth from '@/plugins/auth';
- import { ElMessage, ElMessageBox } from "element-plus";
- import { parseTime } from "@/utils/ruoyi";
- import WarningWorkflowActions from "../WarningWorkflowActions.vue";
- import { getAlertTypeDictionaryData, getTodoListData, getWarningDetailData, readWarningNotification, resolveWarningData, returnWarningData } from "@/api/smxyjldczxt/yjxxfb/yjfb.js";
- const loading = ref(false);
- const activeTab = ref("TODO");
- const todoList = ref([]);
- const doneList = ref([]);
- const noticeList = ref([]);
- const todoTotal = ref(0);
- const doneTotal = ref(0);
- const noticeTotal = ref(0);
- const warningTypeOptions = ref([]);
- const warningLevelOptions = ref([]);
- const detailVisible = ref(false);
- const detailData = ref({});
- const selectedNotice = ref("");
- const actionLoading = ref(false);
- const resolveDialog = reactive({
- visible: false,
- row: null,
- disposalContent: ""
- });
- const returnDialog = reactive({
- visible: false,
- row: null,
- disposalContent: ""
- });
- const queryParams = reactive({
- warningNo: "",
- warningSpecial: "",
- warningType: "",
- warningLevel: "",
- handler: "",
- publisher: "",
- alarmCode: "",
- ownershipUnit: "",
- dateRange: []
- });
- const pagination = reactive({
- pageNum: 1,
- pageSize: 10
- });
- const warningTypeLabelMap = computed(() => {
- return warningTypeOptions.value.reduce((acc, item) => {
- acc[item.dictValue] = item.dictLabel;
- return acc;
- }, {});
- });
- const warningLevelLabelMap = computed(() => {
- return warningLevelOptions.value.reduce((acc, item) => {
- acc[item.dictValue] = item.dictLabel;
- return acc;
- }, {});
- });
- const currentList = computed(() => ({ TODO: todoList.value, DONE: doneList.value, NOTICE: noticeList.value })[activeTab.value] || []);
- const currentTotal = computed(() => ({ TODO: todoTotal.value, DONE: doneTotal.value, NOTICE: noticeTotal.value })[activeTab.value] || 0);
- const todoCount = computed(() => todoTotal.value);
- const doneCount = computed(() => doneTotal.value);
- const noticeCount = computed(() => noticeTotal.value);
- const pagedList = computed(() => currentList.value);
- function getStatusText(status) {
- return databaseStatusLabels.value[status] || status || "-"
- }
- function getStatusTagType(status) {
- if (status === "CLOSED") return "success";
- if (status === "DRAFT" || status === "PENDING") return "info";
- return "warning";
- }
- const detailBasic = computed(() => {
- const basic = detailData.value.basicInfo || {};
- const warningType = basic.warningType || basic.warning_type || "";
- const warningLevel = basic.warningLevel || basic.warning_level || "";
- return {
- warningNo: basic.warningNo || basic.warning_no || "",
- warningSpecial: basic.warningSpecial || basic.warning_special || "",
- warningTypeText: warningTypeLabelMap.value[warningType] || warningType || "-",
- warningLevelText: warningLevelLabelMap.value[warningLevel] || "-",
- handler: basic.handler || "",
- publisher: basic.publisher || "",
- ownershipUnit: basic.ownershipUnit || basic.ownership_unit || "",
- publishTime: basic.publishTime || basic.publish_time || "",
- warningContent: basic.warningContent || basic.warning_content || "",
- remark: basic.remark || ""
- };
- });
- function formatDateTime(value) {
- return value ? parseTime(value) : "-";
- }
- function getLevelTagType(level) {
- return {
- "1": "danger",
- "2": "warning",
- "3": "success",
- "4": "info"
- }[level] || "info";
- }
- async function loadDicts() {
- const [typeResult, levelResult] = await Promise.allSettled([
- getAlertTypeDictionaryData({ dictType: "alert_type" }),
- getAlertTypeDictionaryData({ dictType: "alert_level" })
- ]);
- if (typeResult.status === "fulfilled") {
- warningTypeOptions.value = typeResult.value.rows || [];
- }
- if (levelResult.status === "fulfilled") {
- warningLevelOptions.value = levelResult.value.rows || [];
- }
- }
- function enrichTodoRows(records) {
- return (records || []).map(item => {
- const warningType = item.warning_type || item.warningType || "";
- const warningLevel = item.warning_level || item.warningLevel || "";
- return {
- ...item,
- todoId: item.todo_id || item.todoId,
- warningId: item.warning_id || item.warningId,
- warningNo: item.warning_no || item.warningNo || "",
- warningSpecial: item.warning_special || item.warningSpecial || "",
- warningType,
- warningTypeText: warningTypeLabelMap.value[warningType] || warningType || "-",
- warningLevel,
- warningLevelText: warningLevelLabelMap.value[warningLevel] || "-",
- ownershipUnit: item.ownership_unit || item.ownershipUnit || "",
- publishTime: item.publish_time || item.publishTime || "",
- reviewer: item.reviewer || item.publisher || "",
- alarmCodeList: String(item.alarm_numbers || item.alarmNumbers || "").split(",").filter(Boolean),
- availableActions: item.availableActions || [],
- todoType: item.todo_type || item.todoType,
- actionType: item.action_type || item.actionType,
- taskName: item.task_name || item.taskName || "",
- readStatus: item.read_status || item.readStatus || ""
- };
- });
- }
- async function loadTodoList(type) {
- const [startTime, endTime] = queryParams.dateRange || [];
- const res = await getTodoListData({
- pageNum: pagination.pageNum,
- pageSize: pagination.pageSize,
- todoType: type,
- warningNo: queryParams.warningNo || undefined,
- warningSpecial: queryParams.warningSpecial || undefined,
- warningType: queryParams.warningType || undefined,
- warningLevel: queryParams.warningLevel || undefined,
- handler: queryParams.handler || undefined,
- publisher: queryParams.publisher || undefined,
- alarmCode: queryParams.alarmCode || undefined,
- ownershipUnit: queryParams.ownershipUnit || undefined,
- startTime: startTime || undefined,
- endTime: endTime || undefined
- });
- const records = res.data?.records || [];
- return { rows: enrichTodoRows(records), total: Number(res.data?.total || 0) };
- }
- async function loadCurrentTabData() {
- loading.value = true;
- try {
- if (activeTab.value === "TODO") {
- const result = await loadTodoList("TODO");
- todoList.value = result.rows;
- todoTotal.value = result.total;
- } else {
- const result = await loadTodoList(activeTab.value);
- if (activeTab.value === "NOTICE") {
- noticeList.value = result.rows;
- noticeTotal.value = result.total;
- } else {
- doneList.value = result.rows;
- doneTotal.value = result.total;
- }
- }
- } finally {
- loading.value = false;
- }
- }
- async function preloadCounts() {
- loading.value = true;
- try {
- const [todoResult, doneResult, noticeResult] = await Promise.allSettled([loadTodoList("TODO"), loadTodoList("DONE"), loadTodoList("NOTICE")]);
- if (todoResult.status === "fulfilled") {
- todoList.value = todoResult.value.rows;
- todoTotal.value = todoResult.value.total;
- }
- if (doneResult.status === "fulfilled") {
- doneList.value = doneResult.value.rows;
- doneTotal.value = doneResult.value.total;
- }
- if (noticeResult.status === "fulfilled") {
- noticeList.value = noticeResult.value.rows;
- noticeTotal.value = noticeResult.value.total;
- }
- } finally {
- loading.value = false;
- }
- }
- function handleQuery() {
- pagination.pageNum = 1;
- refreshLists();
- }
- function resetQuery() {
- queryParams.warningNo = "";
- queryParams.warningSpecial = "";
- queryParams.warningType = "";
- queryParams.warningLevel = "";
- queryParams.handler = "";
- queryParams.publisher = "";
- queryParams.alarmCode = "";
- queryParams.ownershipUnit = "";
- queryParams.dateRange = [];
- pagination.pageNum = 1;
- refreshLists();
- }
- function handlePageChange() {
- loadCurrentTabData();
- }
- function handleTabChange() {
- pagination.pageNum = 1;
- loadCurrentTabData();
- }
- async function refreshLists() {
- await preloadCounts();
- if (pagination.pageNum > 1 && currentList.value.length === 0) {
- pagination.pageNum = Math.max(1, Math.ceil(currentTotal.value / pagination.pageSize));
- await preloadCounts();
- }
- }
- async function handleViewDetail(row) {
- if (!auth.hasPermi('warning:warning:query')) return;
- const res = await getWarningDetailData(row.warningId);
- detailData.value = res.data || {};
- selectedNotice.value = activeTab.value === "NOTICE" ? row.taskName : "";
- detailVisible.value = true;
- if (activeTab.value === "NOTICE" && row.readStatus !== "READ") {
- await readWarningNotification(row.todoId);
- row.readStatus = "READ";
- }
- }
- function openResolveDialog(row) {
- resolveDialog.row = row;
- resolveDialog.disposalContent = "";
- resolveDialog.visible = true;
- }
- function openReturnDialog(row) {
- returnDialog.row = row;
- returnDialog.disposalContent = "";
- returnDialog.visible = true;
- }
- async function confirmResolve() {
- if (!resolveDialog.disposalContent) {
- ElMessage.warning("请输入处置说明");
- return;
- }
- actionLoading.value = true;
- try {
- const res = await resolveWarningData({
- warningId: resolveDialog.row.warningId,
- disposalContent: resolveDialog.disposalContent
- });
- if (res.code === 200) {
- ElMessage.success("解除成功");
- resolveDialog.visible = false;
- await refreshLists();
- } else {
- ElMessage.error(res.msg || "解除失败");
- }
- } finally {
- actionLoading.value = false;
- }
- }
- async function confirmReturn() {
- if (!returnDialog.disposalContent) {
- ElMessage.warning("请输入退回说明");
- return;
- }
- actionLoading.value = true;
- try {
- const res = await returnWarningData({
- warningId: returnDialog.row.warningId,
- disposalContent: returnDialog.disposalContent
- });
- if (res.code === 200) {
- ElMessage.success("退回成功");
- returnDialog.visible = false;
- await refreshLists();
- } else {
- ElMessage.error(res.msg || "退回失败");
- }
- } finally {
- actionLoading.value = false;
- }
- }
- onMounted(async () => {
- await loadDicts();
- await preloadCounts();
- });
- </script>
- <style scoped>
- .todo-warning-page {
- background: #f5f7fb;
- }
- .page-card {
- border-radius: 16px;
- margin-bottom: 16px;
- }
- .header-card {
- background: linear-gradient(135deg, #eff6ff, #f8fbff);
- border: 1px solid #dbeafe;
- }
- .page-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 16px;
- }
- .page-title {
- font-size: 22px;
- font-weight: 700;
- color: #1e3a8a;
- }
- .page-subtitle {
- margin-top: 8px;
- font-size: 13px;
- color: #64748b;
- }
- .summary-grid {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 16px;
- margin-bottom: 16px;
- }
- .summary-card {
- border: none;
- color: #fff;
- }
- .summary-card :deep(.el-card__body) {
- padding: 20px;
- }
- .summary-card.danger {
- background: linear-gradient(135deg, #dc2626, #f87171);
- }
- .summary-card.success {
- background: linear-gradient(135deg, #059669, #34d399);
- }
- .summary-card.primary {
- background: linear-gradient(135deg, #2563eb, #5b93ff);
- }
- .summary-label {
- font-size: 14px;
- }
- .summary-value {
- margin: 8px 0 10px;
- font-size: 30px;
- font-weight: 700;
- }
- .summary-desc {
- font-size: 12px;
- opacity: 0.9;
- }
- @media (max-width: 1200px) {
- .summary-grid {
- grid-template-columns: 1fr;
- }
- }
- </style>
|