0.5.1 更新首页

This commit is contained in:
磷叶 2025-02-24 14:56:31 +08:00
parent 3da7554476
commit a0636c093f
15 changed files with 420 additions and 455 deletions

View File

@ -11,4 +11,4 @@ VUE_APP_BASE_API = '/prod-api'
VUE_APP_QINIU_DOMAIN = 'https://api.ccttiot.com'
# WebSocket地址
VUE_APP_WS_HOST = 'wss://pm.chuangtewl.com'
VUE_APP_WS_HOST = 'wss://pm.chuangtewl.com/prod-api'

View File

@ -1,13 +1,5 @@
import request from '@/utils/request'
// 获取本人概览
export function getMineBrief() {
return request({
url: '/dashboard/mineBrief',
method: 'get'
})
}
// 获取概览
export function getBrief(params) {
return request({

View File

@ -43,14 +43,16 @@ export default {
},
backgroundColor() {
const colors = [
'#BBDEFB', //
'#E1BEE7', //
'#C8E6C9', // 绿
'#FFE0B2', //
'#CFD8DC', //
'#B3E5FC', //
'#F8BBD0', //
'#D7CCC8' //
'#90CAF9', //
'#CE93D8', //
'#FFB74D', //
'#81D4FA', //
'#F48FB1', //
'#E57373', //
'#9575CD', //
'#4DB6AC', //
'#FF8A65', //
'#7986CB' //
]
//
const nameStr = this.name || ''

View File

@ -0,0 +1,121 @@
<template>
<el-card class="stat-card" shadow="hover">
<div class="stat-header">
<div class="stat-title">
<i :class="icon" :style="{ color: iconColor }"></i>
<span>{{ title }}</span>
</div>
</div>
<div class="stat-main">
<div class="main-value">
<count-to
:start-val="0"
:end-val="mainValue"
:duration="2000"
class="main-count"
/>
<div class="main-label">{{ mainLabel }}</div>
</div>
</div>
<div class="stat-footer">
<div v-for="(item, index) in items" :key="index" class="footer-item">
<span class="item-label">{{ item.label }}</span>
<count-to
:start-val="0"
:end-val="item.value"
:duration="2000"
class="item-value"
/>
</div>
</div>
</el-card>
</template>
<script>
import CountTo from 'vue-count-to'
export default {
name: 'StatCard',
components: {
CountTo
},
props: {
title: {
type: String,
required: true
},
icon: {
type: String,
required: true
},
iconColor: {
type: String,
default: '#409EFF'
},
mainValue: {
type: Number,
required: true
},
mainLabel: {
type: String,
required: true
},
items: {
type: Array,
default: () => []
}
}
}
</script>
<style lang="scss" scoped>
.stat-card {
height: 100%;
.stat-header {
margin-bottom: 20px;
.stat-title {
display: flex;
align-items: center;
font-size: 16px;
i {
margin-right: 8px;
font-size: 20px;
}
}
}
.stat-main {
margin-bottom: 20px;
.main-value {
text-align: center;
.main-count {
font-size: 36px;
font-weight: bold;
color: #303133;
line-height: 1.2;
}
.main-label {
margin-top: 8px;
font-size: 14px;
color: #909399;
}
}
}
.stat-footer {
display: flex;
justify-content: space-between;
.footer-item {
text-align: center;
.item-label {
display: block;
font-size: 12px;
color: #909399;
margin-bottom: 4px;
}
.item-value {
font-size: 16px;
color: #606266;
}
}
}
}
</style>

View File

@ -110,3 +110,29 @@ export const MessageBstType = {
CUSTOMER: "customer", // 客户
}
// 统计数据键值
export const BriefKeys = {
// 项目
PROJECT_STATUS: "projectStatus", // 项目状态
PROJECT_DEV_COMPLETED: "projectDevCompleted", // 项目开发完成
PROJECT_OVERDUE_DEV_UNCOMPLETED: "projectOverdueDevUncompleted", // 项目逾期开发未完成
PROJECT_OVERDUE_DEV_COMPLETED: "projectOverdueDevCompleted", // 项目逾期开发完成
PROJECT_DEV_OVERDUE: "projectDevOverdue", // 项目开发逾期
PROJECT_DEV_SOON_EXPIRE: "projectDevSoonExpire", // 开发即将到期项目
// 任务
TASK_STATUS: "taskStatus", // 状态数据
TASK_TYPE: "taskType", // 类型分组数据
TASK_OVERDUE_UNCOMPLETED: "taskOverdueUncompleted", // 逾期未完成
TASK_OVERDUE_COMPLETED: "taskOverdueCompleted", // 逾期完成
TASK_TODAY_COMPLETED: "taskTodayCompleted", // 今日完成任务
TASK_SOON_EXPIRE: "taskSoonExpire", // 即将到期任务
// 客户
CUSTOMER_STATUS: "customerStatus", // 状态分组数据
CUSTOMER_TODAY_CREATE: "customerTodayCreate", // 今日新增客户
CUSTOMER_TODAY_FOLLOWED: "customerTodayFollowed", // 今日已跟进客户
CUSTOMER_TODAY_WAIT_FOLLOW: "customerTodayWaitFollow", // 今日待跟进客户
CUSTOMER_SOON_FOLLOW: "customerSoonFollow", // 即将需要跟进的客户
}

View File

@ -222,6 +222,17 @@ export default {
mixins: [$showColumns],
dicts: ['customer_intent_level', 'customer_status'],
components: {FormCol, CustomerLink, CustomerFollowEditDialog},
props: {
query: {
type: Object,
default: () => ({})
},
//
initShowSearch: {
type: Boolean,
default: true
}
},
data() {
return {
showFollowDialog: false,
@ -229,7 +240,7 @@ export default {
span: 24,
//
columns: [
{key: 'id', visible: true, label: '编号', minWidth: null, sortable: true, overflow: false, align: 'center', width: "80"},
{key: 'id', visible: false, label: '编号', minWidth: null, sortable: true, overflow: false, align: 'center', width: "80"},
{key: 'name', visible: true, label: '名称', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'status', visible: true, label: '状态', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'intentLevel', visible: true, label: '意向强度', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
@ -289,6 +300,14 @@ export default {
};
},
created() {
this.initColumns();
this.showSearch = this.initShowSearch;
this.queryParams = {
...this.queryParams,
...this.query
}
this.getList();
},
methods: {

View File

@ -99,9 +99,7 @@ export default {
detail: {},
queryParams: {
pageNum: 1,
pageSize: 5,
orderByColumn: 'top',
isAsc: 'desc'
pageSize: 2,
},
}
},

View File

@ -1,11 +1,32 @@
<template>
<div class="app-container">
<el-row :gutter="12">
<el-col :xs="24" :lg="5">
<panel-group />
</el-col>
<el-col :xs="24" :lg="14">
<project-list-panel/>
<el-col :xs="24" :lg="19" class="mb8">
<el-row :gutter="12" class="mb8">
<el-col :xs="24" :lg="6">
<notice-panel/>
</el-col>
<el-col :xs="24" :lg="18">
<panel-group />
</el-col>
</el-row>
<el-card>
<el-tabs >
<el-tab-pane label="项目列表" lazy v-if="checkPermi(['bst:project:list'])">
<project :hide-columns="projectHideColumns" :init-show-search="false"/>
</el-tab-pane>
<el-tab-pane label="任务列表" lazy v-if="checkPermi(['bst:task:list'])">
<task :hide-columns="taskHideColumns" :init-show-search="false"/>
</el-tab-pane>
<el-tab-pane label="今日完成任务" lazy v-if="checkPermi(['bst:task:list'])">
<task :hide-columns="taskHideColumns" :query="{passDateRange: [today, today]}" :init-show-search="false"/>
</el-tab-pane>
<el-tab-pane label="今日新增客户" lazy v-if="checkPermi(['bst:customer:list'])">
<customer :hide-columns="customerHideColumns" :query="{createDate: today}" :init-show-search="false"/>
</el-tab-pane>
</el-tabs>
</el-card>
<!-- <project-list-panel/> -->
</el-col>
<el-col :xs="24" :lg="5">
<el-card class="card-box">
@ -14,7 +35,6 @@
<el-card class="card-box">
<month-project-chart height="180px" bar-width="50%"/>
</el-card>
<notice-panel/>
</el-col>
</el-row>
</div>
@ -26,13 +46,60 @@ import NoticePanel from '@/views/bst/index/components/NoticePanel.vue';
import MonthProjectChart from '@/views/dashboard/MonthProjectChart.vue';
import ProjectRateChart from '@/views/dashboard/ProjectRateChart.vue';
import ProjectListPanel from './components/ProjectListPanel.vue';
import Project from '@/views/bst/project/index.vue';
import Task from '@/views/bst/task/index.vue';
import Customer from '@/views/bst/customer/index.vue';
import { checkPermi } from '@/utils/permission';
import { parseTime } from '@/utils/ruoyi';
export default {
name: 'Index',
components: { PanelGroup, NoticePanel, MonthProjectChart, ProjectRateChart, ProjectListPanel },
components: { PanelGroup, NoticePanel, MonthProjectChart, ProjectRateChart, ProjectListPanel, Project, Task, Customer },
data() {
return {
today: parseTime(new Date(), '{y}-{m}-{d}'),
projectHideColumns: [
'id',
'no',
'status',
'customerName',
'expireTime',
'expectedCompleteDate',
'maintenanceEndDate',
'ownerName',
'followName',
'amount',
'receivedAmount',
'operationAmount',
'remark',
'createTime'
],
taskHideColumns: [
'id',
'no',
'status',
'level',
'type',
'createId',
'createName',
'createTime',
'remark',
'submitCount',
'passCount',
],
customerHideColumns: [
'id',
'code',
'source',
'followName',
'remark',
'createName',
'createTime'
],
}
},
methods: {
checkPermi,
}
}
</script>

View File

@ -153,7 +153,9 @@ export default {
//
let message = JSON.parse(event.data);
this.showDesktopNotification(message);
this.unreadCount ++;
if (message.id != null) {
this.unreadCount ++;
}
};
this.ws.onclose = (event) => {

View File

@ -117,7 +117,7 @@
/
<el-link class="task-count" type="success" :underline="false">{{ d.row.taskPassCount | dv }}</el-link>
/
<el-link class="task-count" type="warning" :underline="false">{{ d.row.taskWaitConfirmCount | dv }}</el-link>
<el-link class="task-count" type="warning" :underline="false">{{ d.row.taskProcessCount | dv }}</el-link>
</template>
<template v-else-if="['no','name'].includes(column.key)">
<div class="flex-row" style="justify-content: left;">
@ -232,7 +232,12 @@
<!-- 新增任务弹窗 -->
<task-edit-dialog
:show.sync="showTaskDialog"
:init-data="{projectId: row.id}"
:init-data="{
projectId: row.id,
projectOwnerId: row.ownerId,
projectFollowId: row.followId,
projectMemberIds: row.memberIds
}"
@success="getList"
/>
</div>
@ -280,6 +285,10 @@ export default {
initData: {
type: Object,
default: () => {}
},
initShowSearch: {
type: Boolean,
default: true
}
},
data() {
@ -309,7 +318,7 @@ export default {
{key: 'remark', visible: true, label: '备注', minWidth: null, sortable: true, overflow: true, align: 'center', width: null},
{key: 'time', visible: true, label: '截止时间', minWidth: null, sortable: false, overflow: false, align: 'center', width: "100"},
{key: 'createTime', visible: false, label: '创建时间', minWidth: null, sortable: true, overflow: false, align: 'center', width: "100"},
{key: 'taskCount', visible: true, label: '任务(总数/已通过/待审核)', minWidth: null, sortable: true, overflow: false, align: 'center', width: "200"},
{key: 'taskCount', visible: true, label: '任务(总数/已通过/进行中)', minWidth: null, sortable: false, overflow: false, align: 'center', width: "200"},
],
//
orderSorts: ['ascending', 'descending', null],
@ -350,6 +359,7 @@ export default {
},
created() {
this.initColumns();
this.showSearch = this.initShowSearch;
this.queryParams = {
...this.queryParams,

View File

@ -39,6 +39,6 @@ export const ProjectUtils = {
const expireTime = new Date(value);
const now = new Date();
const days = (expireTime - now) / 24 / 60 / 60 / 1000;
return days <= 7 && days > 0;
return days <= 2 && days > 0;
}
}

View File

@ -0,0 +1,43 @@
<template>
<el-alert title="任务统计" type="info" v-loading="loading">
今日完成{{ brief.task.todayCompleted | dv }}
</el-alert>
</template>
<script>
import { getBrief } from '@/api/dashboard/dashboard'
import { BriefKeys } from '@/utils/enums'
export default {
name: 'TaskStatistics',
data() {
return {
loading: false,
brief: {
task: {}
}
}
},
created() {
this.getBrief()
},
methods: {
getBrief() {
this.loading = true
getBrief({ keys: [
BriefKeys.TASK_TODAY_COMPLETED,
]}).then(res => {
if (res.code === 200 && res.data) {
this.brief = res.data
}
}).finally(() => {
this.loading = false
})
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -41,6 +41,17 @@
<el-radio-button :label="false">正常</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="完成日期" prop="passDateRange" v-if="isShow('expireTime')">
<el-date-picker
v-model="queryParams.passDateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
@change="handleQuery"
/>
</el-form-item>
<el-form-item label="创建人" prop="createId" v-if="isShow('createName')">
<user-select v-model="queryParams.createId" @change="handleQuery"/>
</el-form-item>
@ -88,6 +99,10 @@
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row>
<el-row class="mb8">
<task-statistics />
</el-row>
<el-table v-loading="loading" :data="taskList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="onSortChange">
<el-table-column type="selection" width="55" align="center" />
<template v-for="column of showColumns">
@ -119,7 +134,7 @@
<template v-else-if="column.key === 'projectName'">
<project-link :id="d.row.projectId" :text="d.row.projectName"/>
</template>
<template v-else-if="column.key === 'createTime'">
<template v-else-if="column.key === 'expireTime'">
创建:{{d.row.createTime | dv}}<br/>
截止:{{d.row.expireTime | dv}}<br/>
完成:{{d.row.passTime | dv}}<br/>
@ -135,7 +150,7 @@
</template>
</el-table-column>
</template>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180" fixed="right">
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
@ -200,6 +215,7 @@ import AvatarList from '@/components/AvatarList/index.vue';
import UserSelect from '@/components/Business/User/UserSelect.vue';
import {TaskStatus} from '@/utils/enums'
import BooleanTag from '@/components/BooleanTag'
import TaskStatistics from '@/views/bst/task/components/TaskStatistics.vue'
//
const defaultSort = {
prop: "createTime",
@ -210,7 +226,7 @@ export default {
name: "Task",
mixins: [$showColumns, $task],
dicts: ['task_status', 'task_level', 'task_type'],
components: {FormCol, TaskEditDialog, ProjectSelect, TaskViewDialog, ProjectLink, AvatarList, UserSelect, BooleanTag},
components: {FormCol, TaskEditDialog, ProjectSelect, TaskViewDialog, ProjectLink, AvatarList, UserSelect, BooleanTag, TaskStatistics},
props: {
initData: {
type: Object,
@ -219,6 +235,10 @@ export default {
query: {
type: Object,
default: () => ({})
},
initShowSearch: {
type: Boolean,
default: true
}
},
data() {
@ -235,7 +255,7 @@ export default {
{key: 'createName', visible: true, label: '创建人', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'submitCount', visible: true, label: '提交', minWidth: null, sortable: false, overflow: false, align: 'center', width: "50"},
{key: 'passCount', visible: true, label: '完成', minWidth: null, sortable: false, overflow: false, align: 'center', width: "50"},
{key: 'createTime', visible: true, label: '时间', minWidth: null, sortable: true, overflow: false, align: 'left', width: "190"},
{key: 'expireTime', visible: true, label: '时间', minWidth: null, sortable: true, overflow: false, align: 'left', width: "190"},
],
//
orderSorts: ['ascending', 'descending', null],
@ -264,6 +284,7 @@ export default {
pageSize: 20,
orderByColumn: defaultSort.prop,
isAsc: defaultSort.order,
passDateRange: [],
id: null,
projectId: null,
name: null,
@ -281,7 +302,7 @@ export default {
},
created() {
this.initColumns();
this.showSearch = this.initShowSearch;
this.queryParams = {
...this.queryParams,
...this.query

View File

@ -1,269 +1,67 @@
<template>
<div class="dashboard-panel" v-loading="loading">
<!-- 项目统计 -->
<el-card class="card-box" header="参与项目">
<el-row :gutter="10">
<el-col :span="8">
<div class="stat-card project" :class="{'hover': hoveredCard === 'project-total'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-folder"></i>
</div>
<div class="card-label">合计项目</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.project.total || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="stat-card project" :class="{'hover': hoveredCard === 'project-progress'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-refresh"></i>
</div>
<div class="card-label">进行中</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.project.inProgress || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="stat-card project" :class="{'hover': hoveredCard === 'project-completed'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-check"></i>
</div>
<div class="card-label">已完成</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.project.completed || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="stat-card project" :class="{'hover': hoveredCard === 'project-overdue'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-refresh"></i>
</div>
<div class="card-label">维护中</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.project.maintenance || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="stat-card project warning" :class="{'hover': hoveredCard === 'project-overdue'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-warning"></i>
</div>
<div class="card-label">开发超期</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.project.developmentOverdue || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="stat-card project warning" :class="{'hover': hoveredCard === 'project-overdue'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-warning"></i>
</div>
<div class="card-label">维护到期</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.project.maintenanceOverdue || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
</el-row>
</el-card>
<!-- 任务统计 -->
<el-card class="card-box" header="负责任务">
<el-row :gutter="10">
<el-col :span="8">
<div class="stat-card task" :class="{'hover': hoveredCard === 'task-total'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-tickets"></i>
</div>
<div class="card-label">总任务</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.task.total || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="stat-card task" :class="{'hover': hoveredCard === 'task-wait'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-timer"></i>
</div>
<div class="card-label">待完成</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.task.waitCompleted || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="stat-card task" :class="{'hover': hoveredCard === 'task-progress'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-refresh"></i>
</div>
<div class="card-label">进行中</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.task.inProgress || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="stat-card task" :class="{'hover': hoveredCard === 'task-confirm'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-document-checked"></i>
</div>
<div class="card-label">待审核</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.task.waitConfirm || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="stat-card task warning" :class="{'hover': hoveredCard === 'task-confirm'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-warning"></i>
</div>
<div class="card-label">已驳回</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.task.reject || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="stat-card task success" :class="{'hover': hoveredCard === 'task-completed'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-circle-check"></i>
</div>
<div class="card-label">已完成</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.task.completed || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
</el-row>
</el-card>
<!-- 客户统计 -->
<el-card class="card-box" header="我的客户">
<el-row :gutter="10">
<el-col :span="8">
<div class="stat-card customer" :class="{'hover': hoveredCard === 'customer-total'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-user"></i>
</div>
<div class="card-label">总客户</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.customer.total || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="stat-card customer" :class="{'hover': hoveredCard === 'customer-today'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-date"></i>
</div>
<div class="card-label">今日新增</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.customer.today || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="stat-card customer" :class="{'hover': hoveredCard === 'customer-potential'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-user-solid"></i>
</div>
<div class="card-label">潜在客户</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.customer.potential || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="stat-card customer" :class="{'hover': hoveredCard === 'customer-intention'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-star-on"></i>
</div>
<div class="card-label">意向客户</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.customer.intention || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="stat-card customer success" :class="{'hover': hoveredCard === 'customer-transaction'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-shopping-cart-full"></i>
</div>
<div class="card-label">成交客户</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.customer.transaction || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="stat-card customer warning" :class="{'hover': hoveredCard === 'customer-invalid'}">
<div class="card-header">
<div class="card-icon">
<i class="el-icon-close"></i>
</div>
<div class="card-label">失效客户</div>
</div>
<div class="card-value">
<count-to :start-val="0" :end-val="data.customer.invalid || 0" :duration="2000" class="num"/>
</div>
</div>
</el-col>
</el-row>
</el-card>
<el-row :gutter="12">
<el-col :span="8">
<stat-card
title="任务"
icon="el-icon-s-order"
icon-color="#409EFF"
:main-value="data.task.todayCompleted"
main-label="今日完成任务"
:items="[
{ label: '待完成任务', value: data.task.waitCompleted },
{ label: '即将到期', value: data.task.soonExpire },
{ label: '逾期任务', value: data.task.overdueUncompleted }
]"
/>
</el-col>
<el-col :span="8">
<stat-card
title="项目"
icon="el-icon-s-cooperation"
icon-color="#67C23A"
:main-value="data.project.inProgress"
main-label="进行中项目"
:items="[
{ label: '运维中', value: data.project.maintenance },
{ label: '即将到期', value: data.project.devSoonExpire },
{ label: '开发超期', value: data.project.devOverdue }
]"
/>
</el-col>
<el-col :span="8">
<stat-card
title="客户"
icon="el-icon-user"
icon-color="#E6A23C"
:main-value="data.customer.today"
main-label="今日新增客户"
:items="[
{ label: '今日已跟进', value: data.customer.todayFollowed },
{ label: '今日待跟进', value: data.customer.todayWaitFollow },
{ label: '即将跟进', value: data.customer.soonFollow }
]"
/>
</el-col>
</el-row>
</div>
</template>
<script>
import CountTo from 'vue-count-to'
import {getMineBrief} from "@/api/dashboard/dashboard";
import StatCard from '@/components/Dashboard/StatCard'
import { getBrief } from "@/api/dashboard/dashboard"
import { BriefKeys } from '@/utils/enums'
export default {
name: 'PanelGroup',
components: {
CountTo,
StatCard
},
data() {
return {
loading: false,
hoveredCard: '',
data: {
project: {},
task: {},
@ -271,177 +69,33 @@ export default {
}
}
},
components: {
CountTo
},
created() {
this.getData()
},
methods: {
getData() {
this.loading = true;
getMineBrief().then(res => {
this.data = res.data;
this.loading = true
getBrief({
keys: [
BriefKeys.TASK_STATUS,
BriefKeys.TASK_TODAY_COMPLETED,
BriefKeys.TASK_SOON_EXPIRE,
BriefKeys.TASK_OVERDUE_UNCOMPLETED,
BriefKeys.PROJECT_STATUS,
BriefKeys.PROJECT_DEV_SOON_EXPIRE,
BriefKeys.PROJECT_DEV_OVERDUE,
BriefKeys.CUSTOMER_TODAY_CREATE,
BriefKeys.CUSTOMER_TODAY_FOLLOWED,
BriefKeys.CUSTOMER_TODAY_WAIT_FOLLOW,
BriefKeys.CUSTOMER_SOON_FOLLOW,
]
}).then(res => {
this.data = res.data
}).finally(() => {
this.loading = false;
this.loading = false
})
}
}
}
</script>
<style lang="scss" scoped>
.dashboard-panel {
.stat-card {
background: #fff;
border-radius: 4px;
padding: 10px;
text-align: left;
box-shadow: 0 1px 4px rgba(0,0,0,.1);
transition: all .3s;
margin-bottom: 10px;
cursor: pointer;
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
opacity: 1;
transition: opacity .3s;
}
&:hover::before {
opacity: 1.2;
}
.card-header {
display: flex;
align-items: center;
margin-bottom: 6px;
position: relative;
z-index: 1;
.card-icon {
margin-right: 6px;
i {
font-size: 16px;
}
}
.card-label {
font-size: 13px;
color: #909399;
flex: 1;
}
}
.card-value {
position: relative;
z-index: 1;
.num {
font-size: 22px;
font-weight: bold;
color: #1f2f3d;
}
}
&.project {
&::before {
background: linear-gradient(135deg, rgba(128, 100, 255, 0.08) 0%, rgba(255, 255, 255, 0) 45%, rgba(128, 100, 255, 0.1) 100%);
}
.card-icon {
color: #8064ff;
}
}
&.task {
&::before {
background: linear-gradient(135deg, rgba(255, 156, 110, 0.08) 0%, rgba(255, 255, 255, 0) 45%, rgba(255, 156, 110, 0.1) 100%);
}
.card-icon {
color: #ff9c6e;
}
}
&.customer {
&::before {
background: linear-gradient(135deg, rgba(32, 201, 151, 0.08) 0%, rgba(255, 255, 255, 0) 45%, rgba(32, 201, 151, 0.1) 100%);
}
.card-icon {
color: #20c997;
}
}
&.warning {
&::before {
background: linear-gradient(135deg, rgba(255, 77, 79, 0.08) 0%, rgba(255, 255, 255, 0) 45%, rgba(255, 77, 79, 0.1) 100%);
}
.card-icon {
color: #ff4d4f;
}
}
&.success {
&::before {
background: linear-gradient(135deg, rgba(82, 196, 26, 0.08) 0%, rgba(255, 255, 255, 0) 45%, rgba(82, 196, 26, 0.1) 100%);
}
.card-icon {
color: #52c41a;
}
}
&:hover, &.hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,.15);
}
}
}
@media screen and (max-width: 768px) {
.dashboard-panel {
padding: 10px;
.stat-section {
margin-bottom: 15px;
.section-title {
font-size: 14px;
margin-bottom: 10px;
}
.stat-card {
padding: 8px;
.card-header {
margin-bottom: 4px;
.card-icon {
i {
font-size: 14px;
}
}
.card-label {
font-size: 12px;
}
}
.card-value {
.num {
font-size: 18px;
}
}
}
}
}
}
</style>

View File

@ -87,6 +87,7 @@
<script>
import StatisticsCard from '@/components/StatisticsCard'
import { getBrief } from '@/api/dashboard/dashboard'
import { BriefKeys } from '@/utils/enums'
export default {
name: 'UserStatistics',
@ -112,7 +113,16 @@ export default {
methods: {
getBrief() {
this.loading = true
getBrief({ joinUserId: this.userId }).then(res => {
getBrief({ joinUserId: this.userId, keys: [
BriefKeys.PROJECT_STATUS,
BriefKeys.PROJECT_DEV_COMPLETED,
BriefKeys.PROJECT_OVERDUE_DEV_UNCOMPLETED,
BriefKeys.PROJECT_OVERDUE_DEV_COMPLETED,
BriefKeys.TASK_STATUS,
BriefKeys.TASK_TYPE,
BriefKeys.TASK_OVERDUE_UNCOMPLETED,
BriefKeys.TASK_OVERDUE_COMPLETED,
]}).then(res => {
if (res.code === 200 && res.data) {
this.task = res.data.task
this.project = res.data.project