gaoluyang
2026-06-02 7c2cdcbc7f5585b96fba76a07b0e4417a09c4d7e
src/views/collaborativeApproval/noticeManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,960 @@
<template>
  <div class="app-container">
    <!-- æœç´¢è¡¨å• -->
    <div class="search_form">
      <div>
        <el-button type="primary" @click="openForm('add')">新增公告</el-button>
        <el-button type="info" @click="openNoticeTypeDialog">公告类型配置</el-button>
      </div>
    </div>
    <!-- é€šçŸ¥å…¬å‘Šæ¿ -->
    <div class="notice-board">
      <el-tabs v-model="activeNoticeTypeTab" @tab-change="handleNoticeTypeTabChange">
        <el-tab-pane
            v-for="noticeType in noticeTypeList"
            :key="noticeType.id"
            :label="noticeType.noticeType"
            :name="String(noticeType.id)"
        >
          <template #label>
            <span>{{ noticeType.noticeType }}
              <span class="tab-count" v-if="getNoticeCountByType(noticeType.id) > 0">
                ({{ getNoticeCountByType(noticeType.id) }})
              </span>
            </span>
          </template>
          <div class="notice-section">
            <div class="notice-cards">
              <div
                  v-for="notice in getNoticesByType(noticeType.id)"
                  :key="notice.id"
                  class="notice-card"
                  :class="{ 'urgent': notice.priority === '3' }"
              >
                <div class="card-header">
                  <div class="card-title">
                    <el-icon class="notice-icon">
                      <Calendar/>
                    </el-icon>
                    {{ notice.title }}
                  </div>
                  <div class="card-actions">
                    <el-button link type="primary" @click="handleEdit(notice)" :disabled="isNoticeExpired(notice)" v-if="notice.status !== 1">编辑</el-button>
                    <el-button link type="success" @click="handlePublish(notice)" v-if="notice.status === 0">发布</el-button>
                    <el-button link type="danger" @click="handleDelete(notice.id)" v-if="notice.status !== 1">删除</el-button>
                  </div>
                </div>
                <div class="card-content">
                  <p>{{ notice.content }}</p>
                </div>
                <div class="card-footer">
                  <div class="card-meta">
                    <span class="priority" :class="'priority-' + notice.priority">
                      {{ getPriorityText(notice.priority) }}
                    </span>
                    <span class="status" :class="'status-' + getNoticeStatus(notice)">
                      {{ getStatusText(getNoticeStatus(notice)) }}
                    </span>
                  </div>
                  <div class="card-info">
                    <span class="creator">{{ notice.createUserName }}</span>
                    <span class="expiration" v-if="notice.expirationDate">截止日期:{{ notice.expirationDate }}</span>
                  </div>
                </div>
                <div class="card-remark" v-if="notice.remark">
                  <el-icon>
                    <InfoFilled/>
                  </el-icon>
                  <span>{{ notice.remark }}</span>
                </div>
              </div>
            </div>
            <pagination
                v-if="getNoticePageByType(noticeType.id).total > 0"
                :total="getNoticePageByType(noticeType.id).total"
                :page="getNoticePageByType(noticeType.id).current"
                :limit="getNoticePageByType(noticeType.id).size"
                @pagination="(val) => handleNoticeCurrentChange(noticeType.id, val)"
            />
            <!-- ç©ºçŠ¶æ€ -->
            <div class="empty-state" v-if="getNoticesByType(noticeType.id).length === 0">
              <el-empty description="暂无通知公告"/>
            </div>
          </div>
        </el-tab-pane>
      </el-tabs>
    </div>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog
        :title="dialogTitle"
        v-model="dialogVisible"
        width="800px"
        append-to-body
        @close="resetForm"
    >
      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
        <el-row>
          <el-col :span="12">
            <el-form-item label="公告标题" prop="title">
              <el-input v-model="form.title" placeholder="请输入公告标题"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="公告类型" prop="type">
              <el-select v-model="form.type" placeholder="请选择公告类型" style="width: 100%">
                <el-option
                    v-for="item in noticeTypeList"
                    :key="item.id"
                    :label="item.noticeType"
                    :value="item.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="12">
            <el-form-item label="状态">
              <el-radio-group v-model="form.status">
                <el-radio :value="0">草稿</el-radio>
                <el-radio :value="1">正式发布</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="优先级">
              <el-select v-model="form.priority" placeholder="请选择优先级" style="width: 100%">
                <el-option label="普通" :value="1"/>
                <el-option label="重要" :value="2"/>
                <el-option label="紧急" :value="3"/>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="12">
            <el-form-item label="过期时间" prop="expirationDate">
                     <el-date-picker  v-model="form.expirationDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date"
                                              placeholder="请选择" clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="公告内容" prop="content">
              <el-input
                  v-model="form.content"
                  type="textarea"
                  :rows="6"
                  placeholder="请输入公告内容"
                  maxlength="500"
                  show-word-limit
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="备注">
              <el-input
                  v-model="form.remark"
                  type="textarea"
                  :rows="3"
                  placeholder="请输入备注信息"
                  maxlength="200"
                  show-word-limit
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- å…¬å‘Šç±»åž‹é…ç½®å¼¹æ¡† -->
    <el-dialog
        v-model="noticeTypeDialogVisible"
        title="公告类型配置"
        width="800px"
        @close="handleNoticeTypeDialogClose"
    >
      <div class="notice-type-container">
        <div class="notice-type-header">
          <el-button type="primary" @click="handleAddNoticeType">新增类型</el-button>
        </div>
        <el-table :data="noticeTypeList" border style="width: 100%">
          <el-table-column prop="id" label="ID" width="80" align="center"/>
          <el-table-column prop="noticeType" label="公告类型" align="center">
            <template #default="scope">
              <el-input
                  v-if="scope.row.editing"
                  v-model="scope.row.noticeType"
                  placeholder="请输入公告类型"
              />
              <span v-else>{{ scope.row.noticeType }}</span>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="200" align="center">
            <template #default="scope">
              <el-button
                  v-if="scope.row.editing"
                  link
                  type="primary"
                  @click="handleSaveNoticeType(scope.row)"
              >
                ä¿å­˜
              </el-button>
              <el-button
                  v-if="scope.row.editing"
                  link
                  type="info"
                  @click="handleCancelEdit(scope.row)"
              >
                å–消
              </el-button>
              <el-button
                  v-if="!scope.row.editing"
                  link
                  type="primary"
                  @click="handleEditNoticeType(scope.row)"
              >
                ç¼–辑
              </el-button>
              <el-button
                  v-if="!scope.row.editing"
                  link
                  type="danger"
                  @click="handleDeleteNoticeType(scope.row)"
              >
                åˆ é™¤
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </el-dialog>
  </div>
</template>
<script setup>
import {Calendar, InfoFilled} from "@element-plus/icons-vue";
import {onMounted, ref, reactive, toRefs, computed} from "vue";
import {ElMessage, ElMessageBox} from "element-plus";
import {useRoute} from "vue-router";
import useUserStore from "@/store/modules/user";
import {
  addNotice,
  delNotice,
  getCount,
  listNotice,
  updateNotice,
  listNoticeType,
  addNoticeType,
  delNoticeType
} from "../../../api/collaborativeApproval/noticeManagement.js";
import pagination from "../../../components/PIMTable/Pagination.vue";
const userStore = useUserStore();
const route = useRoute();
// å“åº”式数据
const data = reactive({
  searchForm: {
    title: "",
    type: undefined,
    status: undefined,
  },
  form: {
    id: undefined,
    title: "",
    type: null,
    content: "",
    status: 0,
    priority: 1,
    remark: "",
      expirationDate: "",
  },
  rules: {
    title: [
      {required: true, message: "公告标题不能为空", trigger: "blur"}
    ],
    type: [
      {required: true, message: "请选择公告类型", trigger: "change"}
    ],
    content: [
      {required: true, message: "公告内容不能为空", trigger: "blur"}
    ],
      expirationDate: [
      {required: true, message: "请选择日期", trigger: "change"}
    ]
  }
});
const {searchForm, form, rules} = toRefs(data);
// é¡µé¢çŠ¶æ€
const dialogVisible = ref(false);
const dialogTitle = ref("");
const formRef = ref();
// å…¬å‘Šç±»åž‹é…ç½®ç›¸å…³
const noticeTypeDialogVisible = ref(false);
const noticeTypeList = ref([]);
const activeNoticeTypeTab = ref('');
// é€šçŸ¥æ•°æ® - ä½¿ç”¨ Map å­˜å‚¨ï¼Œkey ä¸ºç±»åž‹ id
const noticesMap = ref({});
const noticePagesMap = ref({});
// è®¡ç®—属性
const filteredNotices = computed(() => {
  let filtered = [...mockData];
  if (searchForm.value.noticeTitle) {
    filtered = filtered.filter(item =>
        item.noticeTitle.includes(searchForm.value.noticeTitle)
    );
  }
  if (searchForm.value.noticeType) {
    filtered = filtered.filter(item =>
        item.noticeType === searchForm.value.noticeType
    );
  }
  if (searchForm.value.status !== "") {
    filtered = filtered.filter(item =>
        item.status === searchForm.value.status
    );
  }
  return filtered;
});
// æ–¹æ³•定义
const handleQuery = () => {
  // æœç´¢åŠŸèƒ½ä¿æŒä¸å˜ï¼Œä½†æ•°æ®é€šè¿‡è®¡ç®—å±žæ€§è‡ªåŠ¨è¿‡æ»¤
};
const resetQuery = () => {
  searchForm.value = {
    title: "",
    type: "",
    status: ""
  };
};
const getPriorityText = (priority) => {
  const priorityMap = {"1": "普通", "2": "重要", "3": "紧急"};
  return priorityMap[priority] || "普通";
};
const getStatusText = (status) => {
  const statusMap = {"0": "草稿", "1": "已发布", "2": "已过期"};
  return statusMap[status] || "未知";
};
const isNoticeExpired = (notice) => {
  if (!notice || !notice.expirationDate) {
    return false;
  }
  const expiration = new Date(notice.expirationDate);
  if (Number.isNaN(expiration.getTime())) {
    return false;
  }
  expiration.setHours(23, 59, 59, 999);
  return new Date() > expiration;
};
const getNoticeStatus = (notice) => {
  const normalizedStatus = notice && notice.status !== undefined && notice.status !== null
      ? String(notice.status)
      : "0";
  return isNoticeExpired(notice) ? "2" : normalizedStatus;
};
const openForm = (type) => {
  if (type === 'add') {
    dialogTitle.value = "新增公告";
    form.value = {
      id: undefined,
      title: "",
      type: undefined,
      content: "",
      status: 0,
      priority: 1,
      remark: "",
      expirationDate: "",
    };
  }
  dialogVisible.value = true;
};
const handleEdit = (row) => {
  if (isNoticeExpired(row)) {
    ElMessage.warning("已过期的公告不可编辑");
    return;
  }
  dialogTitle.value = "编辑公告";
  form.value = {...row};
  dialogVisible.value = true;
};
const handleDelete = (id) => {
  ElMessageBox.confirm(
      "确认删除这条公告吗?",
      "提示",
      {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      }
  ).then(() => {
    delNotice(id).then(res => {
      ElMessage.success("删除成功");
      resetTable()
    })
  });
};
const handlePublish = (notice) => {
  ElMessageBox.confirm(
      "确认发布这条公告吗?",
      "提示",
      {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "info"
      }
  ).then(() => {
    updateNotice({
      ...notice,
      status: 1
    }).then(res => {
      if (res.code === 200) {
        ElMessage.success("发布成功");
        resetTable()
      }
    })
  });
};
const submitForm = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      if (form.value.id) {
        // ç¼–辑模式
        updateNotice(form.value).then(res => {
          ElMessage.success("修改成功");
          resetTable()
        })
      } else {
        // æ–°å¢žæ¨¡å¼
        addNotice(form.value).then(res => {
          ElMessage.success("新增成功");
          resetTable()
        })
      }
      dialogVisible.value = false;
    }
  });
};
// åˆå§‹åŒ–某个类型的分页数据
const initNoticePage = (typeId) => {
  if (!noticePagesMap.value[typeId]) {
    noticePagesMap.value[typeId] = {
      total: 0,
      current: 1,
      size: 10
    };
  }
  if (!noticesMap.value[typeId]) {
    noticesMap.value[typeId] = [];
  }
};
// èŽ·å–æŸä¸ªç±»åž‹çš„é€šçŸ¥åˆ—è¡¨
const getNoticesByType = (typeId) => {
  return noticesMap.value[typeId] || [];
};
// èŽ·å–æŸä¸ªç±»åž‹çš„åˆ†é¡µæ•°æ®
const getNoticePageByType = (typeId) => {
  return noticePagesMap.value[typeId] || { total: 0, current: 1, size: 10 };
};
// èŽ·å–æŸä¸ªç±»åž‹çš„æ•°é‡
const getNoticeCountByType = (typeId) => {
  return getNoticePageByType(typeId).total || 0;
};
// èŽ·å–æŸä¸ªç±»åž‹çš„é€šçŸ¥æ•°æ®
const fetchNoticesByType = (typeId) => {
  initNoticePage(typeId);
  const pageData = noticePagesMap.value[typeId];
  listNotice({...pageData, type: typeId}).then(res => {
    if (res.code === 200) {
      noticesMap.value[typeId] = res.data.records || [];
      noticePagesMap.value[typeId].total = res.data.total || 0;
    }
  });
};
// å¤„理分页变化
const handleNoticeCurrentChange = (typeId, val) => {
  initNoticePage(typeId);
  noticePagesMap.value[typeId].size = val.limit;
  noticePagesMap.value[typeId].current = val.page;
  fetchNoticesByType(typeId);
};
// å¤„理 tab åˆ‡æ¢
const handleNoticeTypeTabChange = (tabName) => {
  activeNoticeTypeTab.value = tabName;
  const typeId = Number(tabName);
  fetchNoticesByType(typeId);
};
const resetTable = () => {
  // é‡ç½®æ‰€æœ‰ç±»åž‹çš„分页并重新获取数据
  noticeTypeList.value.forEach(type => {
    initNoticePage(type.id);
    noticePagesMap.value[type.id].current = 1;
    noticePagesMap.value[type.id].size = 10;
    fetchNoticesByType(type.id);
  });
};
const resetForm = () => {
  formRef.value?.resetFields();
};
// å…¬å‘Šç±»åž‹é…ç½®ç›¸å…³æ–¹æ³•
const openNoticeTypeDialog = () => {
  noticeTypeDialogVisible.value = true;
  fetchNoticeTypeList();
};
const fetchNoticeTypeList = () => {
  return listNoticeType().then(res => {
    if (res.code === 200) {
      noticeTypeList.value = res.data.map(item => ({
        ...item,
        editing: false
      }));
      // æ£€æŸ¥è·¯ç”±å‚数中的 type
      const routeType = route.query.type;
      let targetTypeId = null;
      if (routeType) {
        // å¦‚果路由参数中有 type,查找对应的类型
        const typeId = Number(routeType);
        const foundType = noticeTypeList.value.find(item => item.id === typeId);
        if (foundType) {
          targetTypeId = typeId;
        }
      }
      // å¦‚果有类型数据
      if (noticeTypeList.value.length > 0) {
        // å¦‚果路由参数指定了类型且存在,使用路由参数的类型
        // å¦åˆ™å¦‚果没有选中 tab,默认选中第一个
        if (targetTypeId !== null) {
          activeNoticeTypeTab.value = String(targetTypeId);
          fetchNoticesByType(targetTypeId);
        } else if (!activeNoticeTypeTab.value) {
          activeNoticeTypeTab.value = String(noticeTypeList.value[0].id);
          fetchNoticesByType(noticeTypeList.value[0].id);
        }
      }
    }
  });
};
const handleAddNoticeType = () => {
  const newItem = {
    id: undefined,
    noticeType: '',
    editing: true
  };
  noticeTypeList.value.push(newItem);
};
const handleEditNoticeType = (row) => {
  // ä¿å­˜åŽŸå§‹å€¼
  row.originalNoticeType = row.noticeType;
  row.editing = true;
};
const handleSaveNoticeType = (row) => {
  if (!row.noticeType || row.noticeType.trim() === '') {
    ElMessage.warning('公告类型不能为空');
    return;
  }
  const data = {
    noticeType: row.noticeType.trim()
  };
  if (row.id) {
    // ç¼–辑模式 - ä¼ å…¥id
    data.id = row.id;
  }
  addNoticeType(data).then(res => {
    if (res.code === 200) {
      ElMessage.success(row.id ? '编辑成功' : '新增成功');
      row.editing = false;
      delete row.originalNoticeType;
      fetchNoticeTypeList().then(() => {
        // å¦‚果当前选中的类型被编辑,需要重新获取数据
        if (row.id && activeNoticeTypeTab.value === String(row.id)) {
          fetchNoticesByType(res.data?.id || row.id);
        }
      });
    }
  });
};
const handleDeleteNoticeType = (row) => {
  // å¦‚果没有id,说明是新增但未保存的行,直接从前端删除
  if (!row.id) {
    const index = noticeTypeList.value.indexOf(row);
    if (index > -1) {
      noticeTypeList.value.splice(index, 1);
    }
    return;
  }
  // å¦‚果有id,调用后端接口删除
  ElMessageBox.confirm(
      "确认删除这个公告类型吗?",
      "提示",
      {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      }
  ).then(() => {
    delNoticeType(row.id).then(res => {
      if (res.code === 200) {
        ElMessage.success("删除成功");
        // å¦‚果删除的是当前选中的类型,切换到第一个类型
        if (activeNoticeTypeTab.value === String(row.id)) {
          fetchNoticeTypeList().then(() => {
            if (noticeTypeList.value.length > 0) {
              activeNoticeTypeTab.value = String(noticeTypeList.value[0].id);
              fetchNoticesByType(noticeTypeList.value[0].id);
            } else {
              activeNoticeTypeTab.value = '';
            }
          });
        } else {
          fetchNoticeTypeList();
        }
      }
    });
  });
};
const handleCancelEdit = (row) => {
  if (!row.id) {
    // å¦‚果是新增但未保存的行,移除它
    const index = noticeTypeList.value.indexOf(row);
    if (index > -1) {
      noticeTypeList.value.splice(index, 1);
    }
  } else {
    // å¦‚果是编辑中的行,取消编辑状态并恢复原值
    row.editing = false;
    if (row.originalNoticeType !== undefined) {
      row.noticeType = row.originalNoticeType;
      delete row.originalNoticeType;
    }
  }
};
const handleNoticeTypeDialogClose = () => {
  // å…³é—­å¼¹æ¡†æ—¶ï¼Œå–消所有编辑状态
  noticeTypeList.value.forEach(item => {
    if (item.editing && !item.id) {
      // å¦‚果是新增但未保存的行,移除它
      const index = noticeTypeList.value.indexOf(item);
      if (index > -1) {
        noticeTypeList.value.splice(index, 1);
      }
    } else if (item.editing) {
      // å¦‚果是编辑中的行,取消编辑状态并恢复原值
      item.editing = false;
      if (item.originalNoticeType !== undefined) {
        item.noticeType = item.originalNoticeType;
        delete item.originalNoticeType;
      }
    }
  });
};
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  // å…ˆèŽ·å–å…¬å‘Šç±»åž‹åˆ—è¡¨ï¼Œç„¶åŽæ ¹æ®ç±»åž‹èŽ·å–é€šçŸ¥æ•°æ®
  fetchNoticeTypeList();
});
</script>
<style scoped>
.search_form {
  background: #fff;
  padding: 20px;
  margin-bottom: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.search_title {
  font-weight: 500;
  color: #333;
  margin-right: 8px;
}
.ml10 {
  margin-left: 10px;
}
.notice-board {
  background: #f5f7fa;
  padding: 20px;
  border-radius: 8px;
}
.notice-section {
  margin-bottom: 30px;
}
.section-header {
  display: flex;
  align-items: center;
  margin-bottom: 20px;
  padding: 0 10px;
}
.section-header h3 {
  margin: 0;
  color: #303133;
  font-size: 18px;
  font-weight: 600;
}
.section-count {
  margin-left: 10px;
  background: #409eff;
  color: white;
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 12px;
}
.notice-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
  gap: 20px;
}
.notice-card {
  background: white;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  transition: all 0.3s ease;
  border-left: 4px solid transparent;
}
.notice-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.notice-icon {
  color: #409eff;
  margin-right: 8px;
  font-size: 18px;
}
.tab-count {
  color: #909399;
  font-size: 12px;
  margin-left: 4px;
}
.urgent {
  border-left-color: #f56c6c;
  background: linear-gradient(135deg, #fff5f5 0%, #ffffff 100%);
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 15px;
}
.card-title {
  display: flex;
  align-items: center;
  font-size: 16px;
  font-weight: 600;
  color: #303133;
  flex: 1;
}
.card-actions {
  display: flex;
  gap: 8px;
}
.card-content {
  margin-bottom: 15px;
}
.card-content p {
  margin: 0;
  color: #606266;
  line-height: 1.6;
  font-size: 14px;
  word-break: break-all;
  white-space: pre-wrap;
  overflow-wrap: break-word;
}
.card-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}
.card-meta {
  display: flex;
  gap: 8px;
}
.priority, .status {
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 500;
}
.priority-1 {
  background: #f0f9ff;
  color: #0369a1;
}
.priority-2 {
  background: #fef3c7;
  color: #d97706;
}
.priority-3 {
  background: #fef2f2;
  color: #dc2626;
}
.status-0 {
  background: #f3f4f6;
  color: #6b7280;
}
.status-1 {
  background: #d1fae5;
  color: #059669;
}
.status-2 {
  background: #fef3c7;
  color: #d97706;
}
.card-info {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  font-size: 12px;
  color: #909399;
}
.creator {
  font-weight: 500;
  margin-bottom: 2px;
}
.expiration {
  margin-top: 2px;
}
.card-remark {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  background: #f8f9fa;
  border-radius: 6px;
  font-size: 12px;
  color: #606266;
  border-left: 3px solid #409eff;
}
.empty-state {
  text-align: center;
  padding: 60px 20px;
}
.dialog-footer {
  text-align: center;
}
.notice-type-container {
  padding: 10px 0;
}
.notice-type-header {
  margin-bottom: 15px;
  display: flex;
  justify-content: flex-end;
}
/* å“åº”式设计 */
@media (max-width: 768px) {
  .notice-cards {
    grid-template-columns: 1fr;
  }
  .search_form {
    flex-direction: column;
    gap: 15px;
  }
  .search_form > div {
    width: 100%;
  }
}
</style>