| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838 |
- <template>
- <div class="dashboard">
- <div class="header">
- <div class="title-section">
- <h1><i class="fas fa-shield-alt" style="color:#2c5282; margin-right: 8px;"></i>预警中枢</h1>
- <p>员工综合评估(<75分红色预警 | ≥90分优秀)</p>
- </div>
- <div class="badge-group">
- <div class="alert-badge"><i class="fas fa-exclamation-triangle"></i> 实时预警</div>
- <div class="alert-badge" style="border-left-color:#f97316;"><i class="fas fa-chart-line"></i> 动态月度数据
- </div>
- </div>
- </div>
- <div class="filter-bar">
- <div class="time-range">
- <button class="time-btn" :class="{ active: activeRange === 'week' }"
- @click="setActiveRange('week')">近一周</button>
- <button class="time-btn" :class="{ active: activeRange === 'month' }"
- @click="setActiveRange('month')">近一月</button>
- <button class="time-btn" :class="{ active: activeRange === 'quarter' }"
- @click="setActiveRange('quarter')">近三月</button>
- <button class="time-btn" :class="{ active: activeRange === 'year' }"
- @click="setActiveRange('year')">近一年</button>
- <div class="custom-date">
- <el-date-picker v-model="startDate" type="date" placeholder="开始日期" style="width: 150px;" />
- <span style="margin: 0 8px;">至</span>
- <el-date-picker v-model="endDate" type="date" placeholder="结束日期" style="width: 150px;" />
- </div>
- <el-popover trigger="click" placement="bottom" :width="480" ref="treePopoverRef">
- <template #reference>
- <el-input v-model="selectedOrgLabel" placeholder="组织架构/员工" clearable style="width: 480px;"
- @clear="handleTreeClear" />
- </template>
- <el-tree
- :data="cascadeOptions"
- :props="treeProps"
- node-key="value"
- highlight-current
- :current-node-key="selectedOrg"
- @node-click="handleTreeNodeClick"
- />
- </el-popover>
- </div>
- <div class="filter-group">
- <el-select v-model="selectedAlertLevel" placeholder="预警等级" clearable style="width: 250px;">
- <el-option label="全部" value=""></el-option>
- <el-option v-for="item in alert_level" :key="item.value" :label="item.label" :value="item.value" />
- </el-select>
- </div>
- </div>
- <div class="cards-container">
- <div class="cards-grid" style="overflow-x: auto; white-space: nowrap;">
- <div class="card" v-for="(item, index) in summaryCards" :key="index"
- style="display: inline-block; width: 200px; flex-shrink: 0;">
- <div class="card-header">
- <i :class="item.icon"></i>
- <h3>{{ item.title }}</h3>
- <span class="card-badge">{{ item.badge }}</span>
- </div>
- <div class="value-large" :style="{ color: item.color }">{{ item.value }}</div>
- </div>
- </div>
- </div>
- <div class="employee-section">
- <div class="section-title">
- <i class="fas fa-users" style="color:#dc2626;"></i> 员工综合评估预警看板
- <span
- style="font-size:0.7rem; background:#eef2ff; padding:4px 12px; border-radius:30px; margin-left:12px;">
- 评分依据:员工配分表
- </span>
- </div>
- <div class="employee-card">
- <div style="overflow-x: auto;">
- <table class="data-table" style="width:100%;">
- <thead>
- <tr>
- <th>员工ID</th>
- <th>姓名</th>
- <th>所属部门</th>
- <th>综合评估得分</th>
- <th>预警等级</th>
- <th>核心风险/优秀事迹</th>
- <th>状态标签</th>
- </tr>
- </thead>
- <tbody>
- <tr v-for="emp in filteredEmployees" :key="emp.id" :class="getRowClass(emp.overallScore)">
- <td>{{ emp.userId }}</td>
- <td><strong>{{ emp.nickName }}</strong></td>
- <td>{{ emp.deptName }}</td>
- <td>
- <span v-if="emp.overallScore < 75" class="score-danger">{{ emp.overallScore }}
- 分</span>
- <span v-else-if="emp.overallScore >= 90" class="score-excellent">{{ emp.overallScore
- }}
- 分</span>
- <span v-else style="font-weight:600;">{{ emp.overallScore }} 分</span>
- </td>
- <td>
- <span v-if="emp.overallScore < 75" class="status-badge"
- style="animation: subtlePulse 1s infinite;"><i
- class="fas fa-exclamation-triangle"></i> 红色预警</span>
- <span v-else-if="emp.overallScore >= 90" class="status-excellent"
- style="background:#d1fae5; color:#065f46;"><i class="fas fa-star"></i>
- 优秀标杆</span>
- <span v-else class="status-warning">正常范围</span>
- </td>
- <td style="font-size:0.75rem;">{{ emp.coreRisksOrOutstandingAchievements }}</td>
- <td>
- <span v-if="emp.overallScore < 75" style="color:#b91c1c;"><i
- class="fas fa-bell"></i>
- {{ getAlertLabel(emp.statusLabel) }}</span>
- <span v-else-if="emp.overallScore >= 90" style="color:#15803d;"><i
- class="fas fa-crown"></i> {{ getAlertLabel(emp.statusLabel) }}</span>
- <span v-else>{{ getAlertLabel(emp.statusLabel) }}</span>
- </td>
- </tr>
- </tbody>
- </table>
- </div>
- <div class="warning-summary">
- <div><i class="fas fa-circle" style="color:#ef4444;"></i> <strong>红色预警</strong> (综合评估 < 75分) 共计
- {{ redAlertCount }} 人 → 立即约谈/培训</div>
- <div><i class="fas fa-circle" style="color:#22c55e;"></i> <strong>优秀标杆</strong> (综合评估 ≥ 90分) 共计 {{
- excellentCount }} 人 → 表彰激励</div>
- <div><i class="fas fa-chart-simple"></i> 全员平均综合得分: <strong>{{ avgScore }}</strong> 分</div>
- </div>
- </div>
- </div>
- <footer>
- <i class="far fa-bell"></i> 员工部门:旅检一/二/三部 | 阈值:<75红色预警 | ≥90优秀
- </footer>
- </div>
- </template>
- <script setup>
- import { ref, computed, onMounted, watch } from 'vue'
- import { getDeptUserTree } from '@/api/item/items'
- import { getWarningPageData, getEmployeeWarningPageData } from '@/api/warningPage/warningPage'
- import { useDict } from '@/utils/dict'
- import { useRoute } from 'vue-router'
- const route = useRoute()
- const { alert_level } = useDict('alert_level')
- const dateRangeInput = ref(null)
- const activeRange = ref('month')
- const startDate = ref(null)
- const endDate = ref(null)
- const selectedAlertLevel = ref('')
- const selectedOrg = ref('')
- const selectedOrgLabel = ref('')
- const treePopoverRef = ref(null)
- const treeProps = { label: 'label', children: 'children' }
- const cascadeOptions = ref([])
- const summaryCards = ref([
- { icon: 'fas fa-clipboard-list', title: '部门监察问题', badge: '部门级', value: '13项', color: '#b45309' },
- { icon: 'fas fa-microchip', title: '实时质控拦截', badge: '部门级', value: '347次', color: '#2563eb' },
- { icon: 'fas fa-bug', title: '不安全事件', badge: '一级预警', value: '18起', color: '#dc2626' },
- { icon: 'fas fa-shield-virus', title: '安保测试记录', badge: '部门级', value: '4项', color: '#e67e22' },
- { icon: 'fas fa-comment-dots', title: '旅客服务投诉', badge: '服务响应', value: '11件', color: '#e67e22' },
- { icon: 'fas fa-clipboard-check', title: '服务巡查', badge: '部门级', value: '5项', color: '#333' },
- { icon: 'fas fa-graduation-cap', title: '培训及考试成绩', badge: '平均分数', value: '92.4分', color: '#333' },
- { icon: 'fas fa-plane-departure', title: '航站楼', badge: '吞吐量', value: '2.8万', color: '#059669' },
- { icon: 'fas fa-gift', title: '小额奖励', badge: '奖励次数', value: '156次', color: '#7c3aed' }
- ])
- const newNames = [
- "刘孟", "王苡衡", "周海涵", "何欣怡", "王崎",
- "黄政", "刘韵儿", "汪安霖", "张诗敏", "张维", "陈凯琳"
- ]
- const deptList = ["旅检一部", "旅检二部", "旅检三部"]
- const scores = [68, 92, 55, 96, 72, 88, 48, 94, 81, 63, 91]
- const coreRisksOrOutstandingAchievementsList = [
- "违规操作2次+投诉1起,考试成绩62分",
- "优秀服务案例,安保测试满分,无违规",
- "安保测试未通过,不安全事件责任人",
- "典型服务案例主导者,考试成绩98",
- "质控拦截违规2次,考试成绩74分",
- "考试成绩89分,服务巡查良好",
- "严重不规范操作,安保测试未过",
- "质控拦截贡献突出,考试成绩96",
- "表现良好,无安全事故,考试86分",
- "服务巡查扣分,投诉关联2件",
- "临近预警线,服务巡查扣分1次"
- ]
- const employeesData = ref([])
- for (let i = 0; i < newNames.length; i++) {
- employeesData.value.push({
- id: String(10021 + i),
- name: newNames[i],
- dept: deptList[i % deptList.length],
- overallScore: scores[i % scores.length],
- coreRisksOrOutstandingAchievements: coreRisksOrOutstandingAchievementsList[i % coreRisksOrOutstandingAchievementsList.length]
- })
- }
- // 将组织架构数据转换为树形数据
- const transformCascadeData = (nodes) => {
- if (!nodes) return []
- return nodes.map(node => {
- const label = node.nickName || node.deptName || node.name || node.label
- const deptType = node.deptType || node.nodeType
- // console.log(node,"node")
- // let value
- // if (deptType === 'BRIGADE') {
- // value = String(node.deptId)
- // } else if (deptType === 'MANAGER') {
- // value = String(node.teamId)
- // } else if (deptType === 'TEAMS') {
- // value = String(node.groupId)
- // } else {
- // value = String(node.userId ?? node.id ?? node.deptId ?? '')
- // }
- const children = node.children && node.children.length > 0 ? transformCascadeData(node.children) : undefined
- return {
- label,
- value:node.id,
- deptType,
- children
- }
- })
- }
- // 获取选中的部门或用户信息
- const getSelectedInfo = (selectedValue) => {
- if (!selectedValue) return null
- const findNodeByValue = (nodes, value) => {
- for (const node of nodes) {
- if (node.value === value) {
- return node
- }
- if (node.children) {
- const found = findNodeByValue(node.children, value)
- if (found) return found
- }
- }
- return null
- }
- return findNodeByValue(cascadeOptions.value, selectedValue)
- }
- const handleTreeNodeClick = (data) => {
- selectedOrg.value = data.value
- selectedOrgLabel.value = data.label
- treePopoverRef.value?.hide()
- fetchWarningData()
- }
- const handleTreeClear = () => {
- selectedOrg.value = ''
- selectedOrgLabel.value = ''
- fetchWarningData()
- }
- const findNodeLabelByValue = (nodes, value) => {
- if (!nodes) return ''
- for (const node of nodes) {
- if (node.value === value) return node.label
- if (node.children) {
- const found = findNodeLabelByValue(node.children, value)
- if (found) return found
- }
- }
- return ''
- }
- const filteredEmployees = computed(() => {
- let result = employeesData.value.ledgerWarningDetailItemList
- // 预警等级筛选
- if (selectedAlertLevel.value) {
- // 根据字典值判断筛选条件(红色预警 <75,优秀标杆 >=90,正常范围 75-89)
- // 可以根据字典的 value 或 label 来判断
- const alertItem = alert_level.value.find(item => item.value === selectedAlertLevel.value)
- if (alertItem) {
- const label = alertItem.label
- if (label.includes('红色') || label.includes('预警')) {
- result = result.filter(emp => emp.overallScore < 75)
- } else if (label.includes('优秀') || label.includes('标杆')) {
- result = result.filter(emp => emp.overallScore >= 90)
- } else if (label.includes('正常')) {
- result = result.filter(emp => emp.overallScore >= 75 && emp.overallScore < 90)
- }
- }
- }
- return result
- })
- const redAlertCount = computed(() => {
- return employeesData.value?.redAlertNum || 0
- })
- const excellentCount = computed(() => {
- return employeesData.value?.excellentBenchmarkNum || 0
- })
- const avgScore = computed(() => {
- return employeesData.value?.averageComprehensiveScore || 0
- })
- const getRowClass = (score) => {
- if (score < 75) return "employee-warning-row"
- if (score >= 90) return "employee-excellent-row"
- return ""
- }
- const getAlertLabel = (value) => {
- if (!value) return ''
- const item = alert_level.value.find(d => d.value === value)
- return item ? item.label : value
- }
- const setActiveRange = (range) => {
- activeRange.value = range
- // 清除自定义日期
- if (range !== 'custom') {
- startDate.value = null
- endDate.value = null
- }
- }
- const formatDate = (d) => {
- if (!d) return ''
- const date = new Date(d)
- const y = date.getFullYear()
- const m = String(date.getMonth() + 1).padStart(2, '0')
- const day = String(date.getDate()).padStart(2, '0')
- return `${y}-${m}-${day}`
- }
- const getDateRangeFromActive = () => {
- const now = new Date()
- let start = new Date(now)
- if (activeRange.value === 'week') {
- start.setDate(now.getDate() - 7)
- } else if (activeRange.value === 'month') {
- start.setMonth(now.getMonth() - 1)
- } else if (activeRange.value === 'quarter') {
- start.setMonth(now.getMonth() - 3)
- } else {
- start.setFullYear(now.getFullYear() - 1)
- }
- return { startDate: formatDate(start), endDate: formatDate(now) }
- }
- const warningDataMap = {
- ledgerSupervisionProblem: 0,
- ledgerRealtimeInterception: 1,
- ledgerUnsafeEvent: 2,
- ledgerSecurityTest: 3,
- ledgerComplaint: 4,
- ledgerServicePatrol: 5,
- ledgerExamScore: 6,
- ledgerTerminalBonus: 7,
- ledgerRewardApproval: 8
- }
- const fetchWarningData = async () => {
-
- let params = {}
- if (startDate.value && endDate.value) {
- params.startDate = formatDate(startDate.value)
- params.endDate = formatDate(endDate.value)
- } else {
- const range = getDateRangeFromActive()
- params.startDate = range.startDate
- params.endDate = range.endDate
- }
- const selectedInfo = getSelectedInfo(selectedOrg.value)
- if (selectedInfo) {
- if (selectedInfo.deptType === 'BRIGADE') params.deptId = selectedInfo.value
- else if (selectedInfo.deptType === 'MANAGER') params.teamId = selectedInfo.value
- else if (selectedInfo.deptType === 'TEAMS') params.groupId = selectedInfo.value
- else if (selectedInfo.deptType === 'user') params.userId = selectedInfo.value
- }
- try {
- const [r1, r2] = await Promise.all([
- getWarningPageData(params),
- getEmployeeWarningPageData(params)
- ])
- if (r1.data) {
- const d = r1.data
- summaryCards.value.forEach((card, idx) => {
- for (const [key, i] of Object.entries(warningDataMap)) {
- if (i === idx) {
- card.value = d[key] !== undefined ? String(d[key]) : card.value
- break
- }
- }
- })
- }
- if (r2.data) {
- employeesData.value = r2.data
- }
- } catch (error) {
- console.error('获取预警数据失败:', error)
- }
- }
- onMounted(async () => {
- try {
- const res = await getDeptUserTree()
- if (res.data) {
- cascadeOptions.value = transformCascadeData(res.data)
- }
- } catch (error) {
- console.error('获取组织架构数据失败:', error)
- }
- fetchWarningData()
- })
- watch(startDate, () => {
- fetchWarningData()
- })
- watch(endDate, () => {
- fetchWarningData()
- })
- watch(activeRange, () => {
- fetchWarningData()
- })
- watch(cascadeOptions, (val) => {
- if (val.length && selectedOrg.value) {
- selectedOrgLabel.value = findNodeLabelByValue(val, selectedOrg.value)
- }
- })
- // 监听路由参数变化,回显到级联选择器
- watch(() => route.query, (query) => {
- const { id } = query
- if (id) {
- selectedOrg.value = id
- selectedOrgLabel.value = findNodeLabelByValue(cascadeOptions.value, id)
- } else {
- selectedOrg.value = ''
- selectedOrgLabel.value = ''
- }
- fetchWarningData()
- }, { immediate: true })
- </script>
- <style scoped>
- @keyframes subtlePulse {
- 0% {
- opacity: 0.7;
- }
- 50% {
- opacity: 1;
- background: #ffb3b3;
- }
- 100% {
- opacity: 0.7;
- }
- }
- .dashboard {
- max-width: 100%;
- padding: 10px;
- }
- .header {
- margin-bottom: 28px;
- display: flex;
- justify-content: space-between;
- align-items: flex-end;
- flex-wrap: wrap;
- gap: 16px;
- }
- .title-section h1 {
- font-size: 1.7rem;
- font-weight: 700;
- background: linear-gradient(135deg, #1e3c72, #2a5298);
- -webkit-background-clip: text;
- background-clip: text;
- color: transparent;
- letter-spacing: -0.3px;
- }
- .title-section p {
- color: #475569;
- margin-top: 6px;
- font-size: 0.85rem;
- }
- .badge-group {
- display: flex;
- gap: 12px;
- }
- .alert-badge {
- background: #fff1f0;
- border-left: 5px solid #ef4444;
- padding: 6px 16px;
- border-radius: 40px;
- font-weight: 600;
- font-size: 0.85rem;
- }
- .alert-badge i {
- color: #ef4444;
- margin-right: 6px;
- }
- .filter-bar {
- background: white;
- border-radius: 60px;
- padding: 8px 20px;
- margin-bottom: 28px;
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- justify-content: space-between;
- gap: 16px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.02), 0 1px 2px rgba(0, 0, 0, 0.03);
- border: 1px solid #eef2f6;
- }
- .time-range {
- display: flex;
- gap: 8px;
- align-items: center;
- flex-wrap: wrap;
- }
- .time-btn {
- background: #f8fafc;
- border: 1px solid #e2e8f0;
- padding: 6px 18px;
- border-radius: 40px;
- font-size: 0.8rem;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.2s;
- color: #1e293b;
- }
- .time-btn.active {
- background: #2563eb;
- border-color: #2563eb;
- color: white;
- }
- .time-btn:hover {
- background: #e2e8f0;
- }
- .custom-date {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .clear-btn {
- background: transparent;
- border: 1px solid #e2e8f0;
- padding: 4px 8px;
- border-radius: 40px;
- cursor: pointer;
- color: #64748b;
- }
- .clear-btn:hover {
- background: #f1f5f9;
- }
- .custom-date :deep(.el-date-picker) {
- background: transparent;
- }
- .custom-date :deep(.el-input__wrapper) {
- background: transparent;
- }
- .filter-group {
- display: flex;
- gap: 12px;
- align-items: center;
- }
- .search-wrapper {
- display: flex;
- align-items: center;
- gap: 8px;
- background: #f8fafc;
- padding: 4px 12px;
- border-radius: 40px;
- border: 1px solid #e2e8f0;
- }
- .search-btn {
- background: #2563eb;
- border: none;
- color: white;
- padding: 4px 14px;
- border-radius: 30px;
- font-size: 0.75rem;
- font-weight: 500;
- cursor: pointer;
- transition: 0.2s;
- }
- .search-btn:hover {
- background: #1d4ed8;
- }
- .cards-container {
- width: 100%;
- margin-bottom: 36px;
- overflow-x: auto;
- }
- .cards-grid {
- display: flex;
- gap: 16px;
- flex-wrap: nowrap;
- min-width: max-content;
- }
- .card {
- background: #ffffff;
- border-radius: 20px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.03), 0 1px 2px rgba(0, 0, 0, 0.05);
- transition: all 0.2s;
- border: 1px solid #eef2f6;
- padding: 1rem 0.5rem;
- display: flex;
- flex-direction: column;
- text-align: center;
- min-width: 280px;
- flex-shrink: 0;
- }
- .card:hover {
- transform: translateY(-3px);
- box-shadow: 0 12px 20px -10px rgba(0, 0, 0, 0.1);
- border-color: #cbd5e1;
- }
- .card-header {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 6px;
- border-bottom: none;
- padding-bottom: 0;
- margin-bottom: 12px;
- }
- .card-header i {
- font-size: 1.5rem;
- color: #2563eb;
- }
- .card-header h3 {
- font-size: 0.85rem;
- font-weight: 700;
- margin: 0;
- white-space: nowrap;
- }
- .card-badge {
- font-size: 0.6rem;
- background: #f1f5f9;
- padding: 2px 8px;
- border-radius: 30px;
- margin-top: 4px;
- display: inline-block;
- }
- .value-large {
- font-size: 1.8rem;
- font-weight: 800;
- line-height: 1.2;
- margin: 8px 0 4px 0;
- }
- .employee-section {
- margin-top: 20px;
- width: 100%;
- }
- .section-title {
- font-size: 1.2rem;
- font-weight: 700;
- margin-bottom: 1rem;
- display: flex;
- align-items: center;
- gap: 8px;
- border-left: 5px solid #ef4444;
- padding-left: 14px;
- }
- .employee-card {
- background: #ffffff;
- border-radius: 24px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.03), 0 1px 2px rgba(0, 0, 0, 0.05);
- border: 1px solid #eef2f6;
- padding: 1rem 1.5rem;
- width: 100%;
- }
- .data-table {
- width: 100%;
- border-collapse: collapse;
- font-size: 0.8rem;
- }
- .data-table th {
- text-align: left;
- padding: 10px 6px 8px 0;
- font-weight: 600;
- color: #334155;
- border-bottom: 2px solid #e2e8f0;
- }
- .data-table td {
- padding: 8px 6px 8px 0;
- border-bottom: 1px solid #f1f5f9;
- vertical-align: middle;
- }
- .employee-warning-row {
- background-color: #fff5f5;
- border-left: 4px solid #ef4444;
- }
- .employee-excellent-row {
- background-color: #f0fdf4;
- border-left: 4px solid #22c55e;
- }
- .score-danger {
- font-weight: 800;
- color: #dc2626;
- background: #fee2e2;
- padding: 2px 8px;
- border-radius: 30px;
- display: inline-block;
- font-size: 0.8rem;
- }
- .score-excellent {
- font-weight: 800;
- color: #15803d;
- background: #dcfce7;
- padding: 2px 8px;
- border-radius: 30px;
- display: inline-block;
- font-size: 0.8rem;
- }
- .warning-summary {
- background: #fff9f0;
- border-radius: 18px;
- padding: 12px 20px;
- margin-top: 18px;
- display: flex;
- gap: 28px;
- flex-wrap: wrap;
- font-weight: 500;
- font-size: 0.85rem;
- }
- footer {
- text-align: center;
- margin-top: 32px;
- font-size: 0.7rem;
- color: #7e8b9c;
- border-top: 1px solid #e2edf7;
- padding-top: 18px;
- }
- @media (max-width: 1200px) {
- .cards-grid {
- grid-template-columns: repeat(4, 1fr);
- gap: 14px;
- }
- }
- @media (max-width: 800px) {
- .cards-grid {
- grid-template-columns: repeat(2, 1fr);
- }
- body {
- padding: 20px;
- }
- .filter-bar {
- border-radius: 24px;
- flex-direction: column;
- align-items: stretch;
- }
- .org-search {
- justify-content: space-between;
- }
- }
- @media (max-width: 550px) {
- .cards-grid {
- grid-template-columns: 1fr;
- }
- }
- </style>
|