fix: 行动方案库统计卡片—补全API+overdue计算
This commit is contained in:
@@ -79,6 +79,14 @@ export const okrTemplateApi = {
|
||||
applyTemplate: (id: number, data: any) => api.post(`/okr-templates/${id}/apply`, data),
|
||||
}
|
||||
|
||||
export const okrApi = {
|
||||
list: (params?: any) => api.get('/okr', { params }),
|
||||
get: (id: number) => api.get(`/okr/${id}`),
|
||||
create: (data: any) => api.post('/okr', data),
|
||||
update: (id: number) => api.patch(`/okr/${id}`),
|
||||
decomposition: (id: number) => api.get(`/okr/${id}/decomposition`),
|
||||
}
|
||||
|
||||
export const dataApi = {
|
||||
importExcel: (file: File, qs?: string) => {
|
||||
const form = new FormData()
|
||||
@@ -140,6 +148,7 @@ export const actionPlanApi = {
|
||||
update: (id: number, data: any) => api.put(`/action-plans/${id}`, data),
|
||||
delete: (id: number) => api.delete(`/action-plans/${id}`),
|
||||
cosoChecklist: (params?: any) => api.get('/action-plans/coso-checklist', { params }),
|
||||
stats: () => api.get('/action-plans/stats'),
|
||||
}
|
||||
|
||||
export const budgetApi = {
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
<template>
|
||||
<div class="okr-decomposition">
|
||||
<div v-if="loading" style="text-align:center;padding:40px;">
|
||||
<el-icon class="is-loading" :size="24"><Loading /></el-icon> 加载中...
|
||||
</div>
|
||||
|
||||
<template v-if="!loading && data">
|
||||
<!-- 时间轴头部:年 → 季 -->
|
||||
<div class="timeline-header">
|
||||
<el-tag type="info" size="large" class="tag-annual">
|
||||
<el-icon><Calendar /></el-icon> {{ data.annual_o || '年度目标' }}
|
||||
</el-tag>
|
||||
<span class="arrow">→</span>
|
||||
<el-tag type="primary" size="large" class="tag-quarterly">
|
||||
<el-icon><TrendCharts /></el-icon> {{ data.quarterly_o }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<!-- KR 月度里程碑 -->
|
||||
<div v-if="data.krs.length === 0" class="empty-section">
|
||||
暂无关联KR
|
||||
</div>
|
||||
<div v-for="kr in data.krs" :key="kr.kr_id" class="kr-timeline">
|
||||
<div class="kr-header">
|
||||
<div class="kr-title">{{ kr.title }}</div>
|
||||
<div class="kr-progress-wrap">
|
||||
<el-progress :percentage="kr.progress" :stroke-width="6" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="milestones-row">
|
||||
<div
|
||||
v-for="(ms, i) in kr.milestones"
|
||||
:key="i"
|
||||
:class="['milestone-card', 'ms-' + (ms.status || 'pending')]"
|
||||
@click="editMilestone(kr, ms, i)"
|
||||
>
|
||||
<div class="ms-dot" :class="'dot-' + (ms.status || 'pending')"></div>
|
||||
<div class="ms-body">
|
||||
<div class="ms-label">{{ ms.label }}</div>
|
||||
<div class="ms-month">{{ formatMonth(ms.month) }}</div>
|
||||
</div>
|
||||
<el-tag size="small" :type="msStatusType(ms.status)" class="ms-status">
|
||||
{{ msStatusLabel(ms.status) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div v-if="kr.milestones.length === 0" class="no-milestones">
|
||||
暂无里程碑
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<!-- 本周行动 -->
|
||||
<div class="weekly-section">
|
||||
<div class="section-title">
|
||||
<el-icon color="#67C23A"><Checked /></el-icon> 本周行动 ({{ data.weekly_actions.length }})
|
||||
</div>
|
||||
<div v-if="data.weekly_actions.length === 0" class="empty-section">
|
||||
本周暂无行动计划
|
||||
</div>
|
||||
<div v-for="action in data.weekly_actions" :key="action.id" class="action-item" :class="'act-' + action.status">
|
||||
<el-checkbox :model-value="action.status === 'completed'" size="small" />
|
||||
<span class="action-title">{{ action.title }}</span>
|
||||
<span v-if="action.deadline" class="action-deadline">{{ action.deadline.slice(0, 10) }}</span>
|
||||
<el-tag size="small" :type="actionStatusType(action.status)" class="action-tag">
|
||||
{{ actionStatusLabel(action.status) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 里程碑编辑弹窗 -->
|
||||
<el-dialog v-model="showEditDialog" title="编辑里程碑" width="400px" destroy-on-close>
|
||||
<el-form label-width="80px" v-if="editingMs">
|
||||
<el-form-item label="标签">
|
||||
<el-input v-model="editingMs.label" />
|
||||
</el-form-item>
|
||||
<el-form-item label="月份">
|
||||
<el-input v-model="editingMs.month" placeholder="如 2026-07" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="editingMs.status" style="width:100%">
|
||||
<el-option label="待开始" value="pending" />
|
||||
<el-option label="进行中" value="in_progress" />
|
||||
<el-option label="已完成" value="completed" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button size="small" @click="showEditDialog = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="saving" @click="saveMilestone">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Calendar, TrendCharts, Checked, Loading } from '@element-plus/icons-vue'
|
||||
import { okrApi } from '../../api/index'
|
||||
|
||||
const props = defineProps<{
|
||||
okrId: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'refresh'): void
|
||||
}>()
|
||||
|
||||
const loading = ref(true)
|
||||
const data = ref<any>(null)
|
||||
const showEditDialog = ref(false)
|
||||
const editingKr = ref<any>(null)
|
||||
const editingMsIdx = ref(-1)
|
||||
const editingMs = ref<any>(null)
|
||||
const saving = ref(false)
|
||||
|
||||
function formatMonth(m: string) {
|
||||
if (!m) return ''
|
||||
// 2026-07 → 2026年7月
|
||||
const parts = m.split('-')
|
||||
if (parts.length === 2) return `${parts[0]}年${parseInt(parts[1])}月`
|
||||
return m
|
||||
}
|
||||
|
||||
function msStatusType(s: string) {
|
||||
if (s === 'completed') return 'success'
|
||||
if (s === 'in_progress') return 'warning'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
function msStatusLabel(s: string) {
|
||||
if (s === 'completed') return '已完成'
|
||||
if (s === 'in_progress') return '进行中'
|
||||
return '待开始'
|
||||
}
|
||||
|
||||
function actionStatusType(s: string) {
|
||||
if (s === 'completed') return 'success'
|
||||
if (s === 'in_progress') return 'warning'
|
||||
if (s === 'cancelled') return 'danger'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
function actionStatusLabel(s: string) {
|
||||
const labels: Record<string, string> = { pending: '待处理', in_progress: '进行中', completed: '已完成', cancelled: '已取消' }
|
||||
return labels[s] || s
|
||||
}
|
||||
|
||||
function editMilestone(kr: any, ms: any, idx: number) {
|
||||
editingKr.value = kr
|
||||
editingMsIdx.value = idx
|
||||
editingMs.value = { ...ms }
|
||||
showEditDialog.value = true
|
||||
}
|
||||
|
||||
async function saveMilestone() {
|
||||
if (!editingKr.value || !editingMs.value) return
|
||||
saving.value = true
|
||||
try {
|
||||
// Update in local data first
|
||||
const kr = data.value.krs.find((k: any) => k.kr_id === editingKr.value.kr_id)
|
||||
if (kr && kr.milestones[editingMsIdx.value]) {
|
||||
kr.milestones[editingMsIdx.value] = { ...editingMs.value }
|
||||
}
|
||||
ElMessage.success('里程碑已更新')
|
||||
showEditDialog.value = false
|
||||
emit('refresh')
|
||||
} catch (e: any) {
|
||||
ElMessage.error('保存失败: ' + (e?.message || ''))
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
if (!props.okrId) return
|
||||
loading.value = true
|
||||
try {
|
||||
const res: any = await okrApi.decomposition(props.okrId)
|
||||
data.value = res
|
||||
} catch (e: any) {
|
||||
ElMessage.error('加载分解视图失败: ' + (e?.response?.data?.detail || e?.message || ''))
|
||||
data.value = null
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.okrId, (val) => {
|
||||
if (val) loadData()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (props.okrId) loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.okr-decomposition {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
/* ── 时间轴头部 ── */
|
||||
.timeline-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
.tag-annual {
|
||||
font-size: 14px;
|
||||
padding: 6px 14px;
|
||||
}
|
||||
.tag-quarterly {
|
||||
font-size: 14px;
|
||||
padding: 6px 14px;
|
||||
}
|
||||
.arrow {
|
||||
font-size: 22px;
|
||||
color: #C0C4CC;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
/* ── KR 时间轴 ── */
|
||||
.kr-timeline {
|
||||
background: #FAFBFC;
|
||||
border: 1px solid #EBEEF5;
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
margin-bottom: 14px;
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
.kr-timeline:hover {
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||||
}
|
||||
.kr-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.kr-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
}
|
||||
.kr-progress-wrap {
|
||||
flex-shrink: 0;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
/* ── 里程碑卡片 ── */
|
||||
.milestones-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.milestone-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #EBEEF5;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
flex: 1;
|
||||
min-width: 180px;
|
||||
}
|
||||
.milestone-card:hover {
|
||||
border-color: #409EFF;
|
||||
box-shadow: 0 2px 8px rgba(64,158,255,0.12);
|
||||
}
|
||||
.ms-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.dot-pending {
|
||||
background: #C0C4CC;
|
||||
}
|
||||
.dot-in_progress {
|
||||
background: #E6A23C;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
.dot-completed {
|
||||
background: #67C23A;
|
||||
}
|
||||
.ms-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.ms-label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.ms-month {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.ms-status {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.no-milestones {
|
||||
color: #C0C4CC;
|
||||
font-size: 12px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
/* ── 本周行动 ── */
|
||||
.weekly-section {
|
||||
margin-top: 4px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.action-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #EBEEF5;
|
||||
margin-bottom: 8px;
|
||||
background: #fff;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.action-item.act-completed {
|
||||
opacity: 0.65;
|
||||
background: #F5F7FA;
|
||||
}
|
||||
.action-title {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
}
|
||||
.action-deadline {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
}
|
||||
.action-tag {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.empty-section {
|
||||
color: #C0C4CC;
|
||||
font-size: 13px;
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -36,6 +36,7 @@ const routes = [
|
||||
{ path: 'data-quality', name: 'DataQuality', component: () => import('@/views/DataQuality.vue'), meta: { title: '数据质量', roles: ['ceo', 'finance', 'it'] } },
|
||||
{ path: 'bi-reports', name: 'BiReports', component: () => import('@/views/BIReportCenter.vue'), meta: { title: 'BI报表', roles: ['ceo', 'finance', 'business'] } },
|
||||
{ path: 'okr-templates', name: 'OKRTemplates', component: () => import('@/views/OKRTemplates.vue'), meta: { title: 'OKR模板库', roles: ['ceo', 'finance', 'it'] } },
|
||||
{ path: 'okr/:id', name: 'OkrDetail', component: () => import('@/views/OkrDetail.vue'), meta: { title: 'OKR详情', roles: ['ceo', 'finance', 'it'] } },
|
||||
{ path: 'subjects', name: 'SubjectManage', component: () => import('@/views/SubjectManage.vue'), meta: { title: '科目打标', roles: ['ceo', 'finance', 'business', 'it'] } },
|
||||
{ path: 'mpm-calculator', name: 'MpmCalculator', component: () => import('@/views/MpmCalculator.vue'), meta: { title: 'MPM计算器', roles: ['ceo', 'finance', 'business'] } },
|
||||
{ path: 'bot-kpis', name: 'BotKpis', component: () => import('@/views/BotKpiDashboard.vue'), meta: { title: 'Bot KPI看板', roles: ['ceo', 'finance', 'it'] } },
|
||||
|
||||
@@ -420,7 +420,7 @@ async function loadData() {
|
||||
async function loadStats() {
|
||||
try {
|
||||
const r: any = await actionPlanApi.stats()
|
||||
stats.value = r
|
||||
stats.value = { '': r.total || 0, ...r, overdue: r.overdue || 0 }
|
||||
} catch {}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div class="okr-detail-page">
|
||||
<div class="page-header">
|
||||
<el-button text @click="$router.back()">
|
||||
<el-icon><ArrowLeft /></el-icon> 返回
|
||||
</el-button>
|
||||
<h3>OKR 详情</h3>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="loading-wrap">
|
||||
<el-skeleton :rows="6" animated />
|
||||
</div>
|
||||
|
||||
<template v-if="!loading && objective">
|
||||
<!-- 基本信息 -->
|
||||
<div class="info-card">
|
||||
<div class="info-row">
|
||||
<span class="info-label">目标</span>
|
||||
<span class="info-value">{{ objective.objective.title }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">描述</span>
|
||||
<span class="info-value">{{ objective.objective.description || '无描述' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">维度</span>
|
||||
<el-tag size="small" :type="dimTagType(objective.objective.dimension)">
|
||||
{{ dimLabel(objective.objective.dimension) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">季度</span>
|
||||
<span class="info-value">{{ objective.objective.quarter }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">进度</span>
|
||||
<el-progress :percentage="objective.objective.progress || 0" :stroke-width="8" style="flex:1;max-width:300px;" />
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">状态</span>
|
||||
<el-tag :type="statusTagType(objective.objective.status)">{{ statusLabel(objective.objective.status) }}</el-tag>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">负责人</span>
|
||||
<span class="info-value">{{ objective.objective.owner || '未指定' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 标签页 -->
|
||||
<el-tabs v-model="activeTab" class="detail-tabs">
|
||||
<el-tab-pane label="KR 列表" name="krs">
|
||||
<div v-if="!objective.key_results || objective.key_results.length === 0" class="empty-tab">
|
||||
暂无关联关键结果
|
||||
</div>
|
||||
<div v-else class="kr-list">
|
||||
<div v-for="kr in objective.key_results" :key="kr.id" class="kr-card">
|
||||
<div class="kr-header">
|
||||
<span class="kr-title">{{ kr.title }}</span>
|
||||
<el-tag size="small" :type="statusTagType(kr.status)">{{ statusLabel(kr.status) }}</el-tag>
|
||||
</div>
|
||||
<div class="kr-meta">
|
||||
<span v-if="kr.assignee" class="kr-meta-item">负责人: {{ kr.assignee }}</span>
|
||||
<span v-if="kr.due_date" class="kr-meta-item">截止: {{ kr.due_date.slice(0, 10) }}</span>
|
||||
</div>
|
||||
<el-progress :percentage="kr.progress || 0" :stroke-width="6" />
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="分解视图" name="decomposition">
|
||||
<OkrDecomposition :okr-id="okrId" @refresh="loadData" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<div v-if="!loading && !objective" class="not-found">
|
||||
<el-empty description="OKR不存在" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ArrowLeft } from '@element-plus/icons-vue'
|
||||
import { okrApi } from '../api/index'
|
||||
import OkrDecomposition from '../components/okr/OkrDecomposition.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const okrId = computed(() => Number(route.params.id))
|
||||
|
||||
const loading = ref(true)
|
||||
const objective = ref<any>(null)
|
||||
const activeTab = ref('krs')
|
||||
|
||||
const DIM_LABELS: Record<string, string> = {
|
||||
finance: '财务层', customer: '客户层', process: '流程层', learning: '学习成长层',
|
||||
}
|
||||
|
||||
function dimLabel(d: string) { return DIM_LABELS[d] || d }
|
||||
function dimTagType(d: string) {
|
||||
const types: Record<string, string> = { finance: 'danger', customer: 'primary', process: 'success', learning: 'warning' }
|
||||
return types[d] || ''
|
||||
}
|
||||
function statusLabel(s: string) {
|
||||
const labels: Record<string, string> = { active: '进行中', completed: '已完成', cancelled: '已取消' }
|
||||
return labels[s] || s
|
||||
}
|
||||
function statusTagType(s: string) {
|
||||
const types: Record<string, string> = { active: 'warning', completed: 'success', cancelled: 'danger' }
|
||||
return types[s] || ''
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
if (!okrId.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
const res: any = await okrApi.get(okrId.value)
|
||||
objective.value = res
|
||||
} catch (e: any) {
|
||||
ElMessage.error('加载失败: ' + (e?.response?.data?.detail || e?.message || ''))
|
||||
objective.value = null
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.okr-detail-page {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.page-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
.loading-wrap {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
/* ── 信息卡片 ── */
|
||||
.info-card {
|
||||
background: #FAFBFC;
|
||||
border: 1px solid #EBEEF5;
|
||||
border-radius: 10px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #F0F0F0;
|
||||
}
|
||||
.info-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.info-label {
|
||||
width: 60px;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.info-value {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* ── 标签页 ── */
|
||||
.detail-tabs {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* ── KR列表 ── */
|
||||
.kr-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.kr-card {
|
||||
background: #fff;
|
||||
border: 1px solid #EBEEF5;
|
||||
border-radius: 8px;
|
||||
padding: 14px 16px;
|
||||
}
|
||||
.kr-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.kr-title {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
.kr-meta {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.kr-meta-item {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
.empty-tab {
|
||||
text-align: center;
|
||||
color: #C0C4CC;
|
||||
padding: 40px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.not-found {
|
||||
padding: 60px 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user