refactor(developer-config): 移除开发者配置页面相关代码和文档
- 删除应用配置页面及相关组件,重构路由为 /developer/config/[id].vue - 移除开发者文档页面及其导航与样式实现 - 清理开发者侧功能完善工作日志文件 - 删除全局.gitignore配置文件,清理无用忽略规则 - 优化应用配置页面的参数读取和路由结构,解决刷新404问题 - 解决数据库配置唯一键冲突,调整保存逻辑避免重复插入 - 移除对后端配置加密字段的 secret 标记,修正加密异常问题
This commit is contained in:
@@ -1,89 +1,157 @@
|
||||
<template>
|
||||
<div class="admin-home">
|
||||
<!-- 欢迎横幅 -->
|
||||
<div class="welcome-banner">
|
||||
<div class="welcome-left">
|
||||
<h2 class="welcome-title">🎛️ 平台管理中心</h2>
|
||||
<p class="welcome-sub">欢迎回来,{{ adminName }},今日数据已更新</p>
|
||||
</div>
|
||||
<div class="welcome-right">
|
||||
<a-space>
|
||||
<a-tag color="red" style="font-size:13px;padding:4px 12px">超级管理员</a-tag>
|
||||
<a-button size="small" @click="loadStats" :loading="loadingStats">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
刷新数据
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<script setup lang="ts">
|
||||
definePageMeta({ layout: 'admin' })
|
||||
|
||||
<!-- 核心数据统计 -->
|
||||
<a-row :gutter="[16, 16]">
|
||||
<a-col :xs="12" :sm="12" :md="6" v-for="stat in coreStats" :key="stat.label">
|
||||
<div class="stat-block" :class="stat.color" @click="navigateTo(stat.to)" :style="{ cursor: stat.to ? 'pointer' : 'default' }">
|
||||
<div class="stat-block-header">
|
||||
<span class="stat-block-icon">{{ stat.icon }}</span>
|
||||
<span class="stat-block-label">{{ stat.label }}</span>
|
||||
const { activeTab } = useNav()
|
||||
|
||||
const stats = ref([
|
||||
{ label: '生产工单', value: 128, unit: '单', change: '+12%', up: true, icon: '📋', color: '#6366f1' },
|
||||
{ label: '在制品数量', value: 3, unit: '万', change: '+8%', up: true, icon: '🏭', color: '#10b981' },
|
||||
{ label: '设备利用率', value: 87, unit: '%', change: '+3%', up: true, icon: '⚙️', color: '#f59e0b' },
|
||||
{ label: '订单交付率', value: 96, unit: '%', change: '+1%', up: true, icon: '🚚', color: '#3b82f6' },
|
||||
])
|
||||
|
||||
const quickActions = ref([
|
||||
{ label: '计划排程', icon: '📅', color: 'from-indigo-500 to-purple-500', path: '/admin/production/schedule' },
|
||||
{ label: '生产管控', icon: '🎛️', color: 'from-emerald-500 to-teal-500', path: '/admin/production/control' },
|
||||
{ label: '质量检测', icon: '🔍', color: 'from-amber-500 to-orange-500', path: '/admin/production/quality' },
|
||||
{ label: '设备监控', icon: '📊', color: 'from-blue-500 to-cyan-500', path: '/admin/production/equipment' },
|
||||
{ label: '采购申请', icon: '🛒', color: 'from-pink-500 to-rose-500', path: '/admin/supply/purchase' },
|
||||
{ label: '库存查询', icon: '📦', color: 'from-violet-500 to-purple-500', path: '/admin/supply/warehouse' },
|
||||
])
|
||||
|
||||
const recentOrders = ref([
|
||||
{ id: 'WO2026040901', product: '精密轴承组件 A型', quantity: 500, status: '生产中', progress: 65, startDate: '2026-04-09' },
|
||||
{ id: 'WO2026040802', product: '液压缸体 B型', quantity: 200, status: '待排产', progress: 0, startDate: '2026-04-08' },
|
||||
{ id: 'WO2026040801', product: '传动齿轮组 C型', quantity: 1000, status: '已完成', progress: 100, startDate: '2026-04-07' },
|
||||
{ id: 'WO2026040703', product: '密封圈组件 D型', quantity: 3000, status: '已完成', progress: 100, startDate: '2026-04-06' },
|
||||
{ id: 'WO2026040702', product: '弹簧组件 E型', quantity: 800, status: '已取消', progress: 30, startDate: '2026-04-05' },
|
||||
])
|
||||
|
||||
const qualityAlerts = ref([
|
||||
{ level: 'warning', title: '质检异常:批次 B-20260408-03', desc: '尺寸超出公差范围,已自动隔离', time: '10分钟前' },
|
||||
{ level: 'info', title: '质检报告生成', desc: '批次 A-20260409-01 质检完成', time: '30分钟前' },
|
||||
{ level: 'warning', title: '设备报警:CNC-03', desc: '主轴温度异常,请及时处理', time: '1小时前' },
|
||||
])
|
||||
|
||||
const equipmentStatus = ref([
|
||||
{ name: 'CNC-01', status: '运行中', utilization: 92 },
|
||||
{ name: 'CNC-02', status: '运行中', utilization: 88 },
|
||||
{ name: 'CNC-03', status: '告警', utilization: 0 },
|
||||
{ name: '铣床-01', status: '待机', utilization: 0 },
|
||||
{ name: '铣床-02', status: '运行中', utilization: 75 },
|
||||
])
|
||||
|
||||
const statusMap: Record<string, string> = {
|
||||
'生产中': 'processing',
|
||||
'待排产': 'warning',
|
||||
'已完成': 'success',
|
||||
'已取消': 'default',
|
||||
}
|
||||
const equipStatusMap: Record<string, string> = {
|
||||
'运行中': 'success',
|
||||
'告警': 'error',
|
||||
'待机': 'default',
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<!-- 统计卡片 -->
|
||||
<a-row :gutter="[20, 20]" class="mb-6">
|
||||
<a-col :xs="24" :sm="12" :xl="6" v-for="stat in stats" :key="stat.label">
|
||||
<div class="stat-card" :style="{ '--accent': stat.color }">
|
||||
<div class="stat-header">
|
||||
<span class="stat-icon">{{ stat.icon }}</span>
|
||||
<span :class="['stat-change', stat.up ? 'up' : 'down']">{{ stat.change }}</span>
|
||||
</div>
|
||||
<div class="stat-block-value">
|
||||
<template v-if="loadingStats">
|
||||
<a-skeleton-input :active="true" size="small" style="width:60px" />
|
||||
</template>
|
||||
<template v-else>{{ stat.value }}</template>
|
||||
</div>
|
||||
<div class="stat-block-desc">{{ stat.desc }}</div>
|
||||
<div class="stat-value">{{ stat.value }}<span class="stat-unit">{{ stat.unit }}</span></div>
|
||||
<div class="stat-label">{{ stat.label }}</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 待办事项 + 快速入口 -->
|
||||
<a-row :gutter="[16, 16]">
|
||||
<!-- 待处理事项 -->
|
||||
<a-col :xs="24" :md="12">
|
||||
<div class="panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">🔔 待处理事项</span>
|
||||
</div>
|
||||
<div class="todo-list">
|
||||
<div
|
||||
v-for="todo in todoItems"
|
||||
:key="todo.label"
|
||||
class="todo-item"
|
||||
:class="{ 'todo-item-urgent': todo.urgent }"
|
||||
@click="navigateTo(todo.to)"
|
||||
<a-row :gutter="[20, 20]">
|
||||
<!-- 左侧主体 -->
|
||||
<a-col :xs="24" :xl="16">
|
||||
<!-- 快捷入口 -->
|
||||
<div class="card mb-6">
|
||||
<div class="card-title">快捷入口</div>
|
||||
<div class="quick-grid">
|
||||
<NuxtLink
|
||||
v-for="action in quickActions"
|
||||
:key="action.label"
|
||||
:to="action.path"
|
||||
class="quick-item"
|
||||
>
|
||||
<div class="todo-dot" :class="todo.dotColor"></div>
|
||||
<div class="todo-content">
|
||||
<span class="todo-label">{{ todo.label }}</span>
|
||||
<a-tag :color="todo.tagColor" style="margin-left:8px">
|
||||
<template v-if="loadingStats">...</template>
|
||||
<template v-else>{{ todo.value }}</template>
|
||||
</a-tag>
|
||||
</div>
|
||||
<RightOutlined class="todo-arrow" />
|
||||
</div>
|
||||
<div v-if="!loadingStats && todoItems.every(t => t.value === 0)" class="todo-empty">
|
||||
🎉 暂无待处理事项,一切正常!
|
||||
</div>
|
||||
<div class="quick-icon" :class="action.color">{{ action.icon }}</div>
|
||||
<span class="quick-label">{{ action.label }}</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 工单列表 -->
|
||||
<div class="card">
|
||||
<div class="card-title">近期工单</div>
|
||||
<a-table
|
||||
:dataSource="recentOrders"
|
||||
:pagination="false"
|
||||
size="small"
|
||||
rowKey="id"
|
||||
:scroll="{ x: 600 }"
|
||||
>
|
||||
<a-table-column title="工单编号" dataIndex="id" width="140" />
|
||||
<a-table-column title="产品名称" dataIndex="product" />
|
||||
<a-table-column title="数量" dataIndex="quantity" width="80" align="center" />
|
||||
<a-table-column title="状态" dataIndex="status" width="100" align="center">
|
||||
<template #default="{ text }">
|
||||
<a-tag :color="statusMap[text] === 'success' ? 'success' : statusMap[text] === 'processing' ? 'processing' : statusMap[text] === 'warning' ? 'warning' : 'default'">
|
||||
{{ text }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="进度" dataIndex="progress" width="140" align="center">
|
||||
<template #default="{ record }">
|
||||
<a-progress
|
||||
:percent="record.progress"
|
||||
:status="record.progress === 100 ? 'success' : 'active'"
|
||||
:showInfo="false"
|
||||
size="small"
|
||||
/>
|
||||
<span class="text-xs text-gray-400 ml-2">{{ record.progress }}%</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="开始日期" dataIndex="startDate" width="120" />
|
||||
</a-table>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<!-- 快速导航 -->
|
||||
<a-col :xs="24" :md="12">
|
||||
<div class="panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">⚡ 快速入口</span>
|
||||
<!-- 右侧 -->
|
||||
<a-col :xs="24" :xl="8">
|
||||
<!-- 质量告警 -->
|
||||
<div class="card mb-6">
|
||||
<div class="card-title">质量告警</div>
|
||||
<div class="alert-list">
|
||||
<div v-for="(alert, idx) in qualityAlerts" :key="idx" class="alert-item" :class="alert.level">
|
||||
<div class="alert-header">
|
||||
<span class="alert-dot" :class="alert.level"></span>
|
||||
<span class="alert-title">{{ alert.title }}</span>
|
||||
</div>
|
||||
<div class="alert-desc">{{ alert.desc }}</div>
|
||||
<div class="alert-time">{{ alert.time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quick-grid">
|
||||
<div
|
||||
v-for="item in quickLinks"
|
||||
:key="item.to"
|
||||
class="quick-card"
|
||||
@click="navigateTo(item.to)"
|
||||
>
|
||||
<div class="quick-icon" :style="{ background: item.bg }">{{ item.icon }}</div>
|
||||
<div class="quick-label">{{ item.label }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 设备状态 -->
|
||||
<div class="card">
|
||||
<div class="card-title">设备状态</div>
|
||||
<div class="equip-list">
|
||||
<div v-for="equip in equipmentStatus" :key="equip.name" class="equip-item">
|
||||
<span class="equip-name">{{ equip.name }}</span>
|
||||
<a-tag :color="equipStatusMap[equip.status] === 'success' ? 'success' : equipStatusMap[equip.status] === 'error' ? 'error' : 'default'" size="small">
|
||||
{{ equip.status }}
|
||||
</a-tag>
|
||||
<span class="equip-util">{{ equip.utilization }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -92,180 +160,220 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ReloadOutlined, RightOutlined } from '@ant-design/icons-vue'
|
||||
import { getUserInfo } from '@/api/layout'
|
||||
import { getToken } from '@/utils/token-util'
|
||||
import { pageAppProductAll } from '@/api/app/appProduct'
|
||||
import { pageUsers } from '@/api/system/user/index'
|
||||
import { listAppArticle as listCmsArticle } from '@/api/app/article'
|
||||
import { pageGitAccounts } from '@/api/developer'
|
||||
|
||||
definePageMeta({ layout: 'admin' })
|
||||
useHead({ title: '平台管理首页' })
|
||||
|
||||
const adminName = ref('管理员')
|
||||
const loadingStats = ref(false)
|
||||
|
||||
const coreStats = reactive([
|
||||
{ icon: '📦', label: '应用总数', value: 0, desc: '全平台应用', color: 'blue', to: '/admin/apps' },
|
||||
{ icon: '👥', label: '用户总数', value: 0, desc: '注册用户', color: 'green', to: '/admin/users' },
|
||||
{ icon: '⏳', label: '待审核应用', value: 0, desc: '等待审核中', color: 'orange', to: '/admin/app-review' },
|
||||
{ icon: '🛒', label: '上架应用', value: 0, desc: '市场在售', color: 'purple', to: '/admin/market' },
|
||||
])
|
||||
|
||||
const todoItems = reactive([
|
||||
{ label: '待审核应用', value: 0, to: '/admin/app-review', tagColor: 'orange', dotColor: 'dot-orange', urgent: false },
|
||||
{ label: '待审核Git账号', value: 0, to: '/admin/git-review', tagColor: 'cyan', dotColor: 'dot-cyan', urgent: false },
|
||||
{ label: '草稿文章', value: 0, to: '/admin/articles', tagColor: 'blue', dotColor: 'dot-blue', urgent: false },
|
||||
{ label: '冻结用户', value: 0, to: '/admin/users', tagColor: 'red', dotColor: 'dot-red', urgent: false },
|
||||
])
|
||||
|
||||
const ANNOUNCE_MODEL = 'announcement'
|
||||
|
||||
const quickLinks = [
|
||||
{ to: '/admin/app-review', icon: '🔍', label: '应用审核', bg: '#fff7ed' },
|
||||
{ to: '/admin/git-review', icon: '🔧', label: 'Git 审核', bg: '#ecfdf5' },
|
||||
{ to: '/admin/apps', icon: '📦', label: '应用管理', bg: '#eff6ff' },
|
||||
{ to: '/admin/market', icon: '🛒', label: '应用市场', bg: '#faf5ff' },
|
||||
{ to: '/admin/users', icon: '👥', label: '用户管理', bg: '#f0fdf4' },
|
||||
{ to: '/admin/developers', icon: '🧑💻', label: '开发者', bg: '#f0f9ff' },
|
||||
{ to: '/admin/tickets', icon: '🎫', label: '工单处理', bg: '#fdf4ff' },
|
||||
{ to: '/admin/articles', icon: '📝', label: '文章管理', bg: '#fefce8' },
|
||||
{ to: '/admin/article-categories', icon: '🗂️', label: '文章分类', bg: '#ecfeff' },
|
||||
{ to: '/admin/announcements', icon: '📢', label: '公告管理', bg: '#fff1f2' },
|
||||
{ to: '/admin/settings', icon: '⚙️', label: '平台设置', bg: '#f9fafb' },
|
||||
]
|
||||
|
||||
async function loadStats() {
|
||||
loadingStats.value = true
|
||||
try {
|
||||
const [appsRes, usersRes, pendingRes, marketRes, draftRes, frozenRes, gitPendingRes] = await Promise.allSettled([
|
||||
pageAppProductAll({ current: 1, size: 1 }),
|
||||
pageUsers({ page: 1, limit: 1 }),
|
||||
pageAppProductAll({ current: 1, size: 1, publishStatus: 'pending_review' }),
|
||||
pageAppProductAll({ current: 1, size: 1, publishStatus: 'published' }),
|
||||
listCmsArticle({ status: 1 }),
|
||||
pageUsers({ page: 1, limit: 1, status: 1 }),
|
||||
pageGitAccounts({ page: 1, size: 1, status: 'pending' }),
|
||||
])
|
||||
|
||||
coreStats[0].value = appsRes.status === 'fulfilled' ? appsRes.value?.count || 0 : 0
|
||||
coreStats[1].value = usersRes.status === 'fulfilled' ? usersRes.value?.count || 0 : 0
|
||||
coreStats[2].value = pendingRes.status === 'fulfilled' ? pendingRes.value?.count || 0 : 0
|
||||
coreStats[3].value = marketRes.status === 'fulfilled' ? marketRes.value?.count || 0 : 0
|
||||
|
||||
const pendingCount = pendingRes.status === 'fulfilled' ? pendingRes.value?.count || 0 : 0
|
||||
const draftCount = draftRes.status === 'fulfilled'
|
||||
? (draftRes.value || []).filter(item => (item.model || '').trim() !== ANNOUNCE_MODEL).length
|
||||
: 0
|
||||
const frozenCount = frozenRes.status === 'fulfilled' ? frozenRes.value?.count || 0 : 0
|
||||
const gitPendingCount = gitPendingRes.status === 'fulfilled' ? (gitPendingRes.value as any)?.data?.data?.total || 0 : 0
|
||||
|
||||
todoItems[0].value = pendingCount
|
||||
todoItems[0].urgent = pendingCount > 0
|
||||
todoItems[1].value = gitPendingCount
|
||||
todoItems[1].urgent = gitPendingCount > 0
|
||||
todoItems[2].value = draftCount
|
||||
todoItems[3].value = frozenCount
|
||||
} catch { /* ignore */ } finally {
|
||||
loadingStats.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const token = getToken()
|
||||
if (!token) return
|
||||
// 并发加载用户信息和统计数据
|
||||
Promise.allSettled([
|
||||
getUserInfo().then(me => {
|
||||
adminName.value = me?.nickname?.trim() || me?.username?.trim() || '管理员'
|
||||
}),
|
||||
loadStats(),
|
||||
])
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-home {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
.dashboard {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* 欢迎横幅 */
|
||||
.welcome-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: linear-gradient(135deg, #1a0f0f 0%, #3d1515 100%);
|
||||
border-radius: 14px;
|
||||
padding: 24px 28px;
|
||||
color: #fff;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
.welcome-title { font-size: 20px; font-weight: 700; color: #fff; margin: 0 0 6px; }
|
||||
.welcome-sub { font-size: 14px; color: rgba(255,255,255,0.7); margin: 0; }
|
||||
|
||||
/* 核心统计块 */
|
||||
.stat-block {
|
||||
padding: 18px 20px;
|
||||
.stat-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.2s;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||
border: 1px solid #f0f0f0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.stat-block:hover { transform: translateY(-2px); box-shadow: 0 6px 16px rgba(0,0,0,0.08); }
|
||||
.stat-block.blue { background: #eff6ff; border-color: #dbeafe; }
|
||||
.stat-block.green { background: #f0fdf4; border-color: #bbf7d0; }
|
||||
.stat-block.orange { background: #fff7ed; border-color: #fed7aa; }
|
||||
.stat-block.purple { background: #faf5ff; border-color: #e9d5ff; }
|
||||
|
||||
.stat-block-header { display: flex; align-items: center; gap: 6px; margin-bottom: 10px; }
|
||||
.stat-block-icon { font-size: 18px; }
|
||||
.stat-block-label { font-size: 13px; color: rgba(0,0,0,0.55); }
|
||||
.stat-block-value { font-size: 32px; font-weight: 800; color: rgba(0,0,0,0.85); line-height: 1.1; margin-bottom: 4px; }
|
||||
.stat-block-desc { font-size: 12px; color: rgba(0,0,0,0.4); }
|
||||
|
||||
/* Panel */
|
||||
.panel { background: #fff; border: 1px solid #f0f0f0; border-radius: 12px; overflow: hidden; }
|
||||
.panel-header { padding: 14px 18px; border-bottom: 1px solid #f5f5f5; }
|
||||
.panel-title { font-size: 14px; font-weight: 600; color: rgba(0,0,0,0.85); }
|
||||
|
||||
/* 待办 */
|
||||
.todo-list { padding: 8px 0; }
|
||||
.todo-item {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
padding: 12px 18px; cursor: pointer; transition: background 0.15s;
|
||||
.stat-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.stat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.stat-change {
|
||||
font-size: 12px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.stat-change.up {
|
||||
color: #10b981;
|
||||
background: #ecfdf5;
|
||||
}
|
||||
|
||||
.stat-change.down {
|
||||
color: #ef4444;
|
||||
background: #fef2f2;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-unit {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #6b7280;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.todo-item:hover { background: #f9fafb; }
|
||||
.todo-item-urgent .todo-label { font-weight: 600; }
|
||||
.todo-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
||||
.dot-orange { background: #f97316; }
|
||||
.dot-blue { background: #3b82f6; }
|
||||
.dot-cyan { background: #06b6d4; }
|
||||
.dot-red { background: #ef4444; }
|
||||
.todo-content { flex: 1; display: flex; align-items: center; }
|
||||
.todo-label { font-size: 14px; color: rgba(0,0,0,0.75); }
|
||||
.todo-arrow { font-size: 11px; color: rgba(0,0,0,0.3); }
|
||||
.todo-empty { text-align: center; padding: 20px 0; color: rgba(0,0,0,0.4); font-size: 14px; }
|
||||
|
||||
/* 快速入口九宫格 */
|
||||
.quick-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1px;
|
||||
background: #f5f5f5;
|
||||
gap: 12px;
|
||||
}
|
||||
.quick-card {
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
gap: 8px; padding: 18px 12px; background: #fff;
|
||||
cursor: pointer; transition: background 0.15s;
|
||||
|
||||
.quick-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px 8px;
|
||||
border-radius: 10px;
|
||||
background: #fafafa;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.quick-card:hover { background: #f9fafb; }
|
||||
|
||||
.quick-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.quick-icon {
|
||||
width: 44px; height: 44px; border-radius: 10px;
|
||||
display: flex; align-items: center; justify-content: center; font-size: 22px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
margin-bottom: 8px;
|
||||
background: linear-gradient(135deg, rgba(99,102,241,0.1), rgba(168,85,247,0.1));
|
||||
}
|
||||
|
||||
.quick-label {
|
||||
font-size: 13px;
|
||||
color: #374151;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.alert-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.alert-item {
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.alert-item.warning {
|
||||
background: #fffbeb;
|
||||
border-left: 3px solid #f59e0b;
|
||||
}
|
||||
|
||||
.alert-item.info {
|
||||
background: #eff6ff;
|
||||
border-left: 3px solid #3b82f6;
|
||||
}
|
||||
|
||||
.alert-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.alert-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.alert-dot.warning {
|
||||
background: #f59e0b;
|
||||
}
|
||||
|
||||
.alert-dot.info {
|
||||
background: #3b82f6;
|
||||
}
|
||||
|
||||
.alert-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.alert-desc {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.alert-time {
|
||||
font-size: 11px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.equip-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.equip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.equip-name {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.equip-util {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
width: 40px;
|
||||
text-align: right;
|
||||
}
|
||||
.quick-label { font-size: 13px; color: rgba(0,0,0,0.75); font-weight: 500; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user