企迹(qiji) 政企周报管理系统 — v0.1
后端: FastAPI + SQLAlchemy 2.0 (async) + Alembic + MinIO + Casdoor + 企微 前端: Vue 3 + Vite + TypeScript + Element Plus + Pinia 功能清单: - 8 张数据表自动建表 / Casdoor OIDC 登录 / 企微静默登录 - 双布局: 移动端(填报) + PC端(汇总管理) - 拜访记录 CRUD + MinIO 照片直传 + 缩略图预览 + 同访人草稿 - 今日纪要 (6 分类) / 工作计划 / 小微商机 / 要客拜访 CRUD - 客户档案: 备注/收支费用/联系人/归属分配/批量转移 - 客户导入导出 + 模板下载 + 搜索/分页/筛选 - 仪表盘: 四卡统计 + 填报进度 (拜访+纪要双维度) - 周报详情: 五 Tab + 按人/客户筛选 + 时间轴 - 用户管理 / 客户经理 PC 端工作台 - 企微: 催办/公告/定时提醒 / 时区修正 - Docker 部署配置 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { dashboardApi } from '@/api/dashboard'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
const loading = ref(false)
|
||||
const stats = ref({ week_visits: 0, work_plans: 0, mini_business: 0, key_visits: 0, week_start: '', week_end: '' })
|
||||
const progress = ref<any[]>([])
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const [sRes, pRes] = await Promise.all([
|
||||
dashboardApi.getStats(),
|
||||
dashboardApi.getProgress(),
|
||||
])
|
||||
stats.value = sRes.data
|
||||
progress.value = pRes.data
|
||||
} catch (e: any) {
|
||||
ElMessage.error('加载仪表盘失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
function goWeeklyReport() {
|
||||
router.push('/weekly-report')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dashboard" v-loading="loading">
|
||||
<h3>仪表盘</h3>
|
||||
<p class="week-range">{{ stats.week_start }} ~ {{ stats.week_end }}</p>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<el-row :gutter="16" style="margin-bottom: 24px">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-value">{{ stats.week_visits }}</div>
|
||||
<div class="stat-label">本周拜访次数</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-value">{{ stats.work_plans }}</div>
|
||||
<div class="stat-label">工作计划条数</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-value">{{ stats.mini_business }}</div>
|
||||
<div class="stat-label">商机跟单条数</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-value">{{ stats.key_visits }}</div>
|
||||
<div class="stat-label">要客拜访条数</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="actions">
|
||||
<el-button type="primary" @click="goWeeklyReport">📋 本周周报详情</el-button>
|
||||
<el-button type="success" v-if="auth.isDirector" @click="router.push('/weekly-report')">📥 导出 Excel</el-button>
|
||||
<el-button @click="router.push('/customers')">👥 客户管理</el-button>
|
||||
</div>
|
||||
|
||||
<!-- Reporting Progress -->
|
||||
<el-card class="progress-card">
|
||||
<template #header>填报进度</template>
|
||||
<div v-if="progress.length === 0" class="empty">暂无数据</div>
|
||||
<div v-for="p in progress" :key="p.manager_id" class="progress-row">
|
||||
<div class="progress-info">
|
||||
<span class="progress-name">
|
||||
{{ p.manager_name }}
|
||||
<el-tag v-if="!p.has_reported_today" type="danger" size="small">⚠ 今日未填</el-tag>
|
||||
<el-tag v-else type="success" size="small">✓ 已填</el-tag>
|
||||
</span>
|
||||
<span class="progress-count">{{ p.visit_count }} / {{ p.expected }} 条</span>
|
||||
</div>
|
||||
<el-progress
|
||||
:percentage="Math.min(100, Math.round((p.visit_count / Math.max(p.expected, 1)) * 100))"
|
||||
:color="p.completed ? '#67c23a' : '#f56c6c'"
|
||||
:stroke-width="18"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dashboard h3 { margin: 0 0 4px; }
|
||||
.week-range { color: #909399; font-size: 13px; margin-bottom: 20px; }
|
||||
.stat-card { text-align: center; }
|
||||
.stat-value { font-size: 36px; font-weight: 700; color: #1a73e8; }
|
||||
.stat-label { font-size: 13px; color: #909399; margin-top: 4px; }
|
||||
.actions { display: flex; gap: 12px; margin-bottom: 24px; }
|
||||
.progress-card { margin-top: 20px; }
|
||||
.progress-row { margin-bottom: 16px; }
|
||||
.progress-info { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
|
||||
.progress-name { display: flex; align-items: center; gap: 8px; font-weight: 500; }
|
||||
.progress-count { font-size: 13px; color: #606266; }
|
||||
.empty { text-align: center; color: #c0c4cc; padding: 24px 0; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user