mes-vue/src/views/dashboard/index.vue
2026-08-07 15:23:45 +08:00

750 lines
18 KiB
Vue

<template>
<div style="margin:15px 10px;">
<n-grid x-gap="20">
<n-gi :span="19">
<n-grid x-gap="20">
<n-gi :span="3" style="padding:10px;border-radius: 10px;box-shadow: 0 0 20px -5px rgba(84, 151, 232, 0.5);background: #fff;">
<n-button :type="tabindex == 1?'success':'tertiary'" style="width: 100%;margin:10px 0 30px;" @click="gettotal(1)">今天</n-button>
<n-button :type="tabindex == 2?'success':'tertiary'" style="width: 100%; margin-bottom: 30px;" @click="gettotal(2)">昨天</n-button>
<n-button :type="tabindex == 3?'success':'tertiary'" style="width: 100%; margin-bottom: 30px;" @click="gettotal(3)">近7天</n-button>
<n-button :type="tabindex == 4?'success':'tertiary'" style="width: 100%; margin-bottom: 20px;" @click="gettotal(4)">近30天</n-button>
</n-gi>
<n-gi :span="21">
<n-grid x-gap="20">
<n-gi @click="topage('/production/myassigtask')" :span="8" style="display: flex;align-items: center;justify-content: space-between;padding:10px;border-radius: 10px;box-shadow: 0 0 20px -5px rgba(84, 151, 232, 0.5);background: #fff;cursor: pointer;">
<div>
<div style="display: flex;align-items: center;font-size: 20px;color: #1890ff;">
<n-icon size="20">
<BarChartOutline />
</n-icon>
<div style="padding-left: 8px;">工单总数</div>
</div>
<div style="color: #1890ff;">
<span style="font-size: 36px;font-weight: bold;">{{totalobj.totalOrders}}</span>
<span style="padding-left: 5px;"></span>
</div>
<div style="font-size: 12px;">所选时间范围内全部工单</div>
</div>
<div style="padding-left: 20px;border-left:1px solid #e1e0e0;">
<div>已完成</div>
<div style="padding-bottom: 20px;color: #161718;">{{totalobj.completedOrders}}</div>
<div>进行中</div>
<div style="color: #161718;">{{ totalobj.inProgressOrders }}</div>
</div>
</n-gi>
<n-gi @click="topage('/production/myassigtask')" :span="8" style="display: flex;align-items: center;justify-content: space-between;padding:10px;border-radius: 10px;box-shadow: 0 0 20px -5px rgba(84, 151, 232, 0.5);background: #fff;cursor: pointer;">
<div>
<div style="display: flex;align-items: center;font-size: 20px;color: #0891b2;">
<n-icon size="20">
<BookmarksOutline />
</n-icon>
<div style="padding-left: 8px;">进行中工单</div>
</div>
<div style="color: #0891b2;">
<span style="font-size: 36px;font-weight: bold;">{{ totalobj.inProgressOrders }}</span>
<span style="padding-left: 5px;"></span>
</div>
<div style="font-size: 12px;">当前正在执行的工单数量</div>
</div>
<div style="padding-left: 20px;border-left:1px solid #e1e0e0;">
<div>已完成</div>
<div style="padding-bottom: 20px;color: #161718;">{{totalobj.completedOrders}}</div>
<div>工单完成率</div>
<div style="color: #161718;">{{totalobj.achievementRate}}%</div>
</div>
</n-gi>
<n-gi @click="topage('/production/myassigtask')" :span="8" style="display: flex;align-items: center;justify-content: space-between;padding:10px;border-radius: 10px;box-shadow: 0 0 20px -5px rgba(84, 151, 232, 0.5);background: #fff;cursor: pointer;">
<div>
<div style="display: flex;align-items: center;font-size: 20px;color: #18181b;">
<n-icon size="20">
<CellularSharp />
</n-icon>
<div style="padding-left: 8px;">工单完成率</div>
</div>
<div style="color: #18181b;">
<span style="font-size: 36px;font-weight: bold;">{{totalobj.achievementRate}}</span>
<span style="padding-left: 5px;">%</span>
</div>
<div style="font-size: 12px;">按工单数量统计的完成比例</div>
</div>
<div style="padding-left: 20px;border-left:1px solid #e1e0e0;">
<div>已结工单</div>
<div style="padding-bottom: 20px;color: #161718;">{{totalobj.completedOrders}}</div>
<div>工单总数</div>
<div style="color: #161718;">{{totalobj.totalOrders}}</div>
</div>
</n-gi>
<n-gi @click="topage('/production/myassigtask')" :span="8" style="display: flex;align-items: center;justify-content: space-between;margin-top: 20px;padding:10px;border-radius: 10px;box-shadow: 0 0 20px -5px rgba(84, 151, 232, 0.5);background: #fff;cursor: pointer;">
<div>
<div style="display: flex;align-items: center;font-size: 20px;color: #15803d;">
<n-icon size="22">
<CubeOutline />
</n-icon>
<div style="padding-left: 8px;">完工数量</div>
</div>
<div style="color: #15803d;">
<span style="font-size: 36px;font-weight: bold;">{{totalobj.completedTotal}}</span>
<span style="padding-left: 5px;"></span>
</div>
<div style="font-size: 12px;">所选时间范围内合格完工产量</div>
</div>
<div style="padding-left: 20px;border-left:1px solid #e1e0e0;">
<div>产能达成率</div>
<div style="padding-bottom: 20px;color: #161718;">{{totalobj.rate}}%</div>
<div>已结工单</div>
<div style="color: #161718;">{{totalobj.completedOrders}}</div>
</div>
</n-gi>
<n-gi :span="8" style="display: flex;align-items: center;justify-content: space-between;margin-top: 20px;padding:10px;border-radius: 10px;box-shadow: 0 0 20px -5px rgba(84, 151, 232, 0.5);background: #fff;">
<div>
<div style="display: flex;align-items: center;font-size: 20px;color: #ea580c;">
<n-icon size="22">
<ReaderOutline />
</n-icon>
<div style="padding-left: 8px;">库存预警</div>
</div>
<div style="color: #ea580c;">
<span style="font-size: 36px;font-weight: bold;">89</span>
<span style="padding-left: 5px;"></span>
</div>
<div style="font-size: 12px;">需关注与处理的库存预警</div>
</div>
<div style="padding-left: 20px;border-left:1px solid #e1e0e0;">
<div>库存周转</div>
<div style="padding-bottom: 20px;color: #161718;">40%</div>
<div>库存总量</div>
<div style="color: #161718;">630</div>
</div>
</n-gi>
<n-gi :span="8" style="display: flex;align-items: center;justify-content: space-between;margin-top: 20px;padding:10px;border-radius: 10px;box-shadow: 0 0 20px -5px rgba(84, 151, 232, 0.5);background: #fff;">
<div>
<div style="display: flex;align-items: center;font-size: 20px;color: #8b8d07;">
<n-icon size="25">
<LayersOutline />
</n-icon>
<div style="padding-left: 8px;">质量概览</div>
</div>
<div style="color: #8b8d07;">
<span style="font-size: 36px;font-weight: bold;">98</span>
<span style="padding-left: 5px;">%</span>
</div>
<div style="font-size: 12px;">所选时间范围内质量表现</div>
</div>
<div style="padding-left: 20px;border-left:1px solid #e1e0e0;">
<div>未关闭异常</div>
<div style="padding-bottom: 20px;color: #161718;">{{ totalobj.openexcepttotal }}</div>
<div>累计异常</div>
<div style="color: #161718;">{{totalobj.exceptiontotal}}</div>
</div>
</n-gi>
</n-grid>
</n-gi>
</n-grid>
</n-gi>
<n-gi :span="5" style="padding:10px;border-radius: 10px;box-shadow: 0 0 20px -5px rgba(84, 151, 232, 0.5);background: #fff;">
<div style="display: flex;align-items: center;font-size: 18px;color: #663706;">
<n-icon size="23">
<Contract />
</n-icon>
<div style="padding-left: 5px;">快捷入口</div>
</div>
<div class="shortcuts-grid">
<div
v-for="n in list"
:key="n.path"
class="shortcut-item"
@click="topage(n.path)"
>
<div class="shortcut-icon" :style="{ background: n.bgColor }">
<n-icon size="24" :color="n.color">
<component :is="n.icon"/>
</n-icon>
</div>
<div class="shortcut-name">{{ n.name }}</div>
</div>
</div>
</n-gi>
</n-grid>
<n-grid style="margin-top: 20px;">
<n-gi :span="24" style="padding:10px;border-radius: 10px;box-shadow: 0 0 20px -5px rgba(84, 151, 232, 0.5);background: #fff;">
<div style="display: flex;align-items: center;justify-content: space-between;">
<div style="display: flex;align-items: center;font-size: 18px;color: #056a83;">
<n-icon size="23">
<DiceOutline />
</n-icon>
<div style="padding-left: 5px;">代办事项</div>
</div>
<div style="display: flex;align-items: center;font-size: 14px;color: #4c7c8b;cursor: pointer;">
<div>查看全部</div>
<n-icon size="18">
<ChevronForward />
</n-icon>
</div>
</div>
</n-gi>
</n-grid>
</div>
</template>
<script setup lang="ts">
import {ref, markRaw, reactive} from 'vue'
import {useRouter} from 'vue-router'
import {
PersonOutline,
PeopleOutline,
Contract,
BarChartOutline,
BookmarksOutline,
CellularSharp,
CubeOutline,
ReaderOutline,
LayersOutline,
DiceOutline,
ChevronForward,
GitNetworkOutline,
ListOutline,
IdCardOutline,
} from '@vicons/ionicons5'
import {
getorders,
getsctotal,
getachievement,
getquality,
getopenquality
} from '@/api/dashboard'
function getDateBeforeToday(days:any, format:any = 'YYYY-MM-DD') {
const date = new Date()
date.setDate(date.getDate() - days)
if (format === 'YYYY-MM-DD') {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
return date;
}
let tabindex = ref(1)
let totalobj = ref<any>({})
let dateRange:any = ''
function gettotal(index:any) {
tabindex.value = index
if(index == 1){
dateRange = getDateBeforeToday(0)
}else if(index == 2){
dateRange = getDateBeforeToday(1)
}else if(index == 3){
dateRange = getDateBeforeToday(7)
}else{
dateRange = getDateBeforeToday(30)
}
//getorders()
getsctotal({dateRange}).then(rps => {
totalobj.value = {
...totalobj.value,
...rps
}
})
//产能达成率
getachievement({dateRange}).then(rps => {
totalobj.value = {
...totalobj.value,
...rps
}
})
//累计质检异常
getquality({dateRange}).then(rps => {
totalobj.value = {
...totalobj.value,
exceptiontotal:rps.total
}
})
//未关闭异常
getopenquality({dateRange}).then(rps => {
totalobj.value = {
...totalobj.value,
openexcepttotal:rps.total
}
})
}
const router = useRouter()
gettotal(1)
function topage(path:any) {
router.push(path)
}
// 快捷入口
const list:any =ref(
[
{
name: '用户管理',
path: '/system/user',
icon: markRaw(PersonOutline),
color: '#111827',
bgColor: '#F3F4F6'
},
{
name: '角色管理',
path: '/system/role',
icon: markRaw(PeopleOutline),
color: '#059669',
bgColor: '#D1FAE5'
},
{
name: '部门管理',
path: '/org/dept',
icon: markRaw(GitNetworkOutline),
color: '#2563EB',
bgColor: '#DBEAFE'
},
{
name: '岗位管理',
path: '/org/post',
icon: markRaw(IdCardOutline),
color: '#7C3AED',
bgColor: '#EDE9FE'
},
{
name: '操作日志',
path: '/log/operlog',
icon: markRaw(ListOutline),
color: '#DC2626',
bgColor: '#FEE2E2'
}
]
)
</script>
<style lang="scss" scoped>
// 欢迎区域
.welcome-section {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.welcome-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 24px;
background: #fff;
border-radius: 12px;
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
}
.welcome-header {
display: flex;
align-items: center;
gap: 16px;
}
.welcome-text {
flex: 1;
}
.welcome-title {
font-size: 22px;
font-weight: 700;
color: #111827;
margin: 0 0 6px 0;
}
.welcome-desc {
font-size: 14px;
color: #6B7280;
margin: 0;
}
.welcome-time {
display: flex;
align-items: baseline;
gap: 12px;
margin-top: 16px;
}
.time-display {
font-size: 36px;
font-weight: 700;
color: #111827;
font-variant-numeric: tabular-nums;
}
.date-display {
font-size: 14px;
color: #6B7280;
}
// 轮播Banner
.welcome-banner {
width: 380px;
flex-shrink: 0;
}
.banner-carousel {
height: 100%;
border-radius: 12px;
overflow: hidden;
:deep(.n-carousel__slides) {
height: 100%;
}
:deep(.n-carousel__slide) {
height: 100%;
}
:deep(.n-carousel__dots) {
bottom: 12px;
}
:deep(.n-carousel__dot) {
background: rgba(255, 255, 255, 0.5);
&.n-carousel__dot--active {
background: #fff;
}
}
:deep(.n-carousel__arrow) {
background: rgba(255, 255, 255, 0.2);
color: #fff;
&:hover {
background: rgba(255, 255, 255, 0.3);
}
}
}
.banner-item {
height: 100%;
min-height: 140px;
padding: 24px;
display: flex;
align-items: center;
}
.banner-content {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.banner-text {
flex: 1;
}
.banner-title {
font-size: 20px;
font-weight: 700;
color: #fff;
margin: 0 0 8px 0;
}
.banner-subtitle {
font-size: 14px;
color: rgba(255, 255, 255, 0.85);
margin: 0;
}
.banner-icon {
opacity: 0.6;
}
.stat-cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 20px;
}
.stat-card {
:deep(.n-card__content) {
padding: 20px;
}
}
.stat-content {
display: flex;
align-items: center;
gap: 16px;
}
.stat-icon {
width: 52px;
height: 52px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.stat-value {
font-size: 28px;
font-weight: 700;
color: #111827;
line-height: 1;
}
.stat-title {
font-size: 14px;
color: #6B7280;
margin-top: 4px;
}
.middle-section {
margin-bottom: 20px;
}
.shortcuts-card {
height: 100%;
}
.shortcuts-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.shortcut-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s;
&:hover {
background: #F3F4F6;
}
}
.shortcut-icon {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
}
.shortcut-name {
font-size: 13px;
color: #374151;
font-weight: 500;
}
.changelog-card {
height: 100%;
:deep(.n-card__content) {
max-height: 280px;
overflow-y: auto;
}
}
.changelog-list {
margin: 0;
padding-left: 16px;
font-size: 13px;
color: #6B7280;
li {
margin-bottom: 4px;
&:last-child {
margin-bottom: 0;
}
}
}
.bottom-section {
margin-bottom: 20px;
}
.system-card {
height: 100%;
:deep(.n-descriptions) {
--n-th-padding: 10px 12px;
--n-td-padding: 10px 12px;
}
}
.author-card {
height: 100%;
}
.author-content {
display: flex;
gap: 20px;
align-items: flex-start;
}
.author-avatar {
flex-shrink: 0;
}
.author-info {
flex: 1;
}
.author-name {
font-size: 18px;
font-weight: 600;
color: #111827;
margin: 0 0 8px 0;
}
.author-desc {
font-size: 14px;
color: #6B7280;
margin: 0 0 12px 0;
line-height: 1.5;
}
.author-links {
margin-top: 8px;
}
.author-link {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
background: #f3f4f6;
border-radius: 6px;
font-size: 13px;
color: #374151;
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
&:hover {
background: #e5e7eb;
color: #111827;
}
}
.wechat-info {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #333;
}
.project-info {
margin-top: 4px;
}
.project-desc {
font-size: 13px;
color: #6B7280;
margin: 0 0 12px 0;
line-height: 1.6;
}
.project-stats {
display: flex;
gap: 24px;
}
.project-stat-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: #374151;
}
@media (max-width: 1200px) {
.welcome-section {
flex-direction: column;
}
.welcome-banner {
width: 100%;
}
.banner-item {
min-height: 120px;
}
.stat-cards {
grid-template-columns: repeat(2, 1fr);
}
.middle-section,
.bottom-section {
:deep(.n-grid) {
display: block;
}
:deep(.n-gi) {
margin-bottom: 20px;
}
}
}
@media (max-width: 768px) {
.welcome-header {
flex-direction: column;
text-align: center;
}
.welcome-time {
flex-direction: column;
align-items: center;
gap: 4px;
}
.stat-cards {
grid-template-columns: 1fr;
}
.shortcuts-grid {
grid-template-columns: repeat(2, 1fr);
}
.author-content {
flex-direction: column;
align-items: center;
text-align: center;
}
.project-stats {
justify-content: center;
flex-wrap: wrap;
}
}
</style>