@@ -23,51 +23,93 @@
< / view >
< / view >
<!-- 报餐统计 卡片 -- >
<!-- 卡片1 : 有效报餐 -- >
< view class = "statistics-card" style = "margin-bottom: 15px;" >
< view class = "card-title" > ✅ 有效报餐 < / view >
< view class = "cross-grid" >
<!-- 第1行 : 表头 -- >
< view class = "stats-item cross-header" > < / view >
< view class = "stats-item cross-header" > 堂食 < / view >
< view class = "stats-item cross-header" > 打包 < / view >
< view class = "stats-item cross-header" > 小计 < / view >
<!-- 第2行 : 吃辣 -- >
< view class = "stats-item cross-label" > 🌶 吃辣 < / view >
< view class = "stats-item" > { { ms . validDine } } < / view >
< view class = "stats-item" > { { ms . validTake } } < / view >
< view class = "stats-item" > { { ms . validTotal } } < / view >
<!-- 第3行 : 不吃辣 -- >
< view class = "stats-item cross-label" > 🌿 不吃辣 < / view >
< view class = "stats-item" > { { mn . validDine } } < / view >
< view class = "stats-item" > { { mn . validTake } } < / view >
< view class = "stats-item" > { { mn . validTotal } } < / view >
<!-- 第4行 : 小计 -- >
< view class = "stats-item cross-label cross-total" > 小计 < / view >
< view class = "stats-item cross-total" > { { ma . validDine } } < / view >
< view class = "stats-item cross-total" > { { ma . validTake } } < / view >
< view class = "stats-item cross-total" > { { ma . validTotal } } < / view >
< / view >
< / view >
<!-- 卡片2 : 无效报餐 -- >
< view class = "statistics-card" style = "margin-bottom: 15px;" >
< view class = "card-title" > ❌ 无效报餐 < / view >
< view class = "cross-grid" >
<!-- 第1行 : 表头 -- >
< view class = "stats-item cross-header" > < / view >
< view class = "stats-item cross-header" > 堂食 < / view >
< view class = "stats-item cross-header" > 打包 < / view >
< view class = "stats-item cross-header" > 小计 < / view >
<!-- 第2行 : 吃辣 -- >
< view class = "stats-item cross-label" > 🌶 吃辣 < / view >
< view class = "stats-item" > { { ms . invalidDine } } < / view >
< view class = "stats-item" > { { ms . invalidTake } } < / view >
< view class = "stats-item" > { { ms . invalidTotal } } < / view >
<!-- 第3行 : 不吃辣 -- >
< view class = "stats-item cross-label" > 🌿 不吃辣 < / view >
< view class = "stats-item" > { { mn . invalidDine } } < / view >
< view class = "stats-item" > { { mn . invalidTake } } < / view >
< view class = "stats-item" > { { mn . invalidTotal } } < / view >
<!-- 第4行 : 小计 -- >
< view class = "stats-item cross-label cross-total" > 小计 < / view >
< view class = "stats-item cross-total" > { { ma . invalidDine } } < / view >
< view class = "stats-item cross-total" > { { ma . invalidTake } } < / view >
< view class = "stats-item cross-total" > { { ma . invalidTotal } } < / view >
< / view >
< / view >
<!-- 卡片3 : 总计 -- >
< view class = "statistics-card" >
< view class = "card-title" > 报餐人数统 计< / view >
< view class = "stat s-grid" >
<!-- 第一行 -- >
< view class = "stats-item" >
< text class = "item-label " > 堂食人数 < / text >
< text class = "item-value" > { { validDineIn + invalidDineIn } } < / text >
< / view >
< view class = "stats-item" >
< text class = "item-label" > 打包人数 < / text >
< text class = "item-value" > { { validTakeout + invalidTakeout } } < / text >
< / view >
< view class = "stats-item" >
< text class = "item-label" > 总人数 < / text >
< text class = "item-value" > { { validTotal + invalidTotal } } < / text >
< / view >
< view class = "card-title" > 📊 总 计< / view >
< view class = "cros s-grid" >
<!-- 第1行 : 表头 -- >
< view class = "stats-item cross-header" > < / view >
< view class = "stats-item cross-header " > 堂食 < / view >
< view class = "stats-item cross-header" > 打包 < / view >
< view class = "stats-item cross-header" > 小计 < / view >
<!-- 第二行 -- >
< view class = "stats-item" >
< text class = "item-label" > 有效堂食人数 < / text >
< text class = "item-value " > { { validDineIn } } < / text >
< / view >
< view class = "stats-item" >
< text class = "item-label" > 有效打包人数 < / text >
< text class = "item-value" > { { validTakeout } } < / text >
< / view >
< view class = "stats-item" >
< text class = "item-label" > 有效总人数 < / text >
< text class = "item-value" > { { validTotal } } < / text >
< / view >
<!-- 第2行 : 吃辣 -- >
< view class = "stats-item cross-label" > 🌶 吃辣 < / view >
< view class = "stats-item" > { { ms . dine } } < / view >
< view class = "stats-item " > { { ms . take } } < / view >
< view class = "stats-item" > { { ms . total } } < / view >
<!-- 第三行 -- >
< view class = "stats-item" >
< text class = "item-label" > 无效堂食人数 < / text >
< text class = "item-value " > { { invalidDineIn } } < / text >
< / view >
< view class = "stats-item" >
< text class = "item-label" > 无效打包人数 < / text >
< text class = "item-value" > { { invalidTakeout } } < / text >
< / view >
< view class = "stats-item" >
< text class = "item-label" > 无效总人数 < / text >
< text class = "item-value" > { { invalidTotal } } < / text >
< / view >
<!-- 第3行 : 不吃辣 -- >
< view class = "stats-item cross-label" > 🌿 不吃辣 < / view >
< view class = "stats-item" > { { mn . dine } } < / view >
< view class = "stats-item " > { { mn . take } } < / view >
< view class = "stats-item" > { { mn . total } } < / view >
<!-- 第4行 : 小计 -- >
< view class = "stats-item cross-label cross-total" > 小计 < / view >
< view class = "stats-item cross-total" > { { ma . dine } } < / view >
< view class = "stats-item cross-total" > { { ma . take } } < / view >
< view class = "stats-item cross-total" > { { ma . total } } < / view >
< / view >
< / view >
@@ -115,6 +157,9 @@
import {
listMealReport
} from "@/api/wms/mealReport" ;
import {
listEmployeeInfo
} from "@/api/wms/employeeInfo" ;
import {
getDicts
} from '@/api/system/dict/data.js'
@@ -127,13 +172,17 @@
data ( ) {
return {
queryParams : {
mealType : '' ,
mealType : undefined ,
reportDate : '' ,
deptName : undefined ,
reportUserName : undefined ,
status : undefined ,
deadlineTime : '16:00:00' ,
pageSize : 9999 ,
pageNum : 1
} ,
deadlineDate : '' , // 截止日期
deadlineTime : '12 :00:00' , // 截止时间(原始值)
deadlineDate : '' ,
deadlineTime : '16 :00:00' , // 截止时间(原始值)
// 时分秒选择器临时变量
timeSelect : {
hour : 12 ,
@@ -141,14 +190,14 @@
second : 0
} ,
list : [ ] ,
employeeSpicyMap : { } , // name -> isSpicyEater (1吃辣/0不吃辣)
loading : false ,
// 统计数据
validDineIn : 0 ,
validTakeout : 0 ,
validTotal : 0 ,
invalidDineIn : 0 ,
invalidTakeout : 0 ,
invalidTotal : 0 ,
// 三维交叉统计矩阵(吃辣×打包×有效)
matrix : {
spicy : { validDine : 0 , validTake : 0 , validTotal : 0 , invalidDine : 0 , invalidTake : 0 , invalidTotal : 0 , dine : 0 , take : 0 , total : 0 } ,
nonSpicy : { validDine : 0 , validTake : 0 , validTotal : 0 , invalidDine : 0 , invalidTake : 0 , invalidTotal : 0 , dine : 0 , take : 0 , total : 0 } ,
all : { validDine : 0 , validTake : 0 , validTotal : 0 , invalidDine : 0 , invalidTake : 0 , invalidTotal : 0 , dine : 0 , take : 0 , total : 0 }
} ,
range : [ ]
}
} ,
@@ -157,7 +206,11 @@
formattedDeadlineTime ( ) {
const [ hour , minute , second ] = this . deadlineTime . split ( ':' ) ;
return ` ${ hour . padStart ( 2 , '0' ) } : ${ minute . padStart ( 2 , '0' ) } : ${ second . padStart ( 2 , '0' ) } ` ;
}
} ,
// 从 matrix 对象中提取快捷访问属性(保持模板简洁)
ms ( ) { return this . matrix . spicy ; } ,
mn ( ) { return this . matrix . nonSpicy ; } ,
ma ( ) { return this . matrix . all ; }
} ,
onLoad ( ) {
// 初始化今日日期
@@ -166,6 +219,8 @@
this . deadlineDate = this . queryParams . reportDate ;
// 获取餐别字典数据
this . getRangeData ( ) ;
// 加载员工信息(含吃辣偏好)
this . getEmployeeList ( ) ;
// 加载报餐数据
this . getList ( ) ;
this . getDeadlineConfig ( ) ;
@@ -182,7 +237,9 @@
getDeadlineConfig ( ) {
getConfigKey ( 'hrm.meal.deadline' ) . then ( response => {
this . queryParams . deadlineT ime = response . msg || '16:00:00'
const t ime = response . msg || '16:00:00' ;
this . queryParams . deadlineTime = time ;
this . deadlineTime = time ;
} )
} ,
@@ -196,6 +253,94 @@
} ) ;
} ,
/** 构建员工吃辣偏好映射( name -> isSpicyEater) */
getEmployeeList ( ) {
listEmployeeInfo ( { pageSize : 9999 , pageNum : 1 } ) . then ( response => {
const map = { } ;
( response . rows || [ ] ) . forEach ( emp => {
// 仅在职且明确吃辣偏好的员工
if ( emp . name && emp . isLeave !== 1 && emp . isSpicyEater !== undefined && emp . isSpicyEater !== null ) {
map [ emp . name ] = emp . isSpicyEater == 1 ? 1 : 0 ;
}
} ) ;
this . employeeSpicyMap = map ;
if ( this . list . length > 0 ) {
this . calcTableSum ( ) ;
}
} ) . catch ( err => {
console . error ( '获取员工信息失败:' , err ) ;
} ) ;
} ,
/** 从逗号分隔的姓名列表中统计不吃辣人数 */
countNonSpicy ( nameList ) {
if ( ! nameList ) return 0 ;
const names = nameList . split ( ',' ) . map ( n => n . trim ( ) ) . filter ( n => n ) ;
let count = 0 ;
const counted = { } ;
names . forEach ( name => {
if ( ! counted [ name ] && this . employeeSpicyMap [ name ] === 0 ) {
count ++ ;
counted [ name ] = true ;
}
} ) ;
return count ;
} ,
/** 三维交叉统计:吃辣/不吃辣 × 堂食/打包 × 有效/无效 */
calcTableSum ( ) {
const m = {
spicy : { validDine : 0 , validTake : 0 , validTotal : 0 , invalidDine : 0 , invalidTake : 0 , invalidTotal : 0 , dine : 0 , take : 0 , total : 0 } ,
nonSpicy : { validDine : 0 , validTake : 0 , validTotal : 0 , invalidDine : 0 , invalidTake : 0 , invalidTotal : 0 , dine : 0 , take : 0 , total : 0 } ,
all : { validDine : 0 , validTake : 0 , validTotal : 0 , invalidDine : 0 , invalidTake : 0 , invalidTotal : 0 , dine : 0 , take : 0 , total : 0 }
} ;
this . list . forEach ( item => {
const dine = item . dineInPeople ? Number ( item . dineInPeople ) : 0 ;
const take = item . takeoutPeople ? Number ( item . takeoutPeople ) : 0 ;
const total = item . totalPeople ? Number ( item . totalPeople ) : 0 ;
// 从姓名列表统计不吃辣人数
const nonSpicyDine = this . countNonSpicy ( item . dineInPeopleList ) ;
const nonSpicyTake = this . countNonSpicy ( item . takeoutPeopleList ) ;
const spicyDine = dine - nonSpicyDine ;
const spicyTake = take - nonSpicyTake ;
const nonSpicyTotal = nonSpicyDine + nonSpicyTake ;
const spicyTotal = spicyDine + spicyTake ;
// 有效/无效
const v = this . isValidMealReport ( item . createTime ) ? 'valid' : 'invalid' ;
// 吃辣
m . spicy [ v + 'Dine' ] += spicyDine ;
m . spicy [ v + 'Take' ] += spicyTake ;
m . spicy [ v + 'Total' ] += spicyTotal ;
m . spicy . dine += spicyDine ;
m . spicy . take += spicyTake ;
m . spicy . total += spicyTotal ;
// 不吃辣
m . nonSpicy [ v + 'Dine' ] += nonSpicyDine ;
m . nonSpicy [ v + 'Take' ] += nonSpicyTake ;
m . nonSpicy [ v + 'Total' ] += nonSpicyTotal ;
m . nonSpicy . dine += nonSpicyDine ;
m . nonSpicy . take += nonSpicyTake ;
m . nonSpicy . total += nonSpicyTotal ;
// 合计
m . all [ v + 'Dine' ] += dine ;
m . all [ v + 'Take' ] += take ;
m . all [ v + 'Total' ] += total ;
m . all . dine += dine ;
m . all . take += take ;
m . all . total += total ;
} ) ;
this . matrix = m ;
console . log ( '【移动端矩阵结果】吃辣总计:' , m . spicy . total , '不吃辣总计:' , m . nonSpicy . total , '合计:' , m . all . total ) ;
console . log ( '【移动端矩阵明细】吃辣:' , JSON . stringify ( m . spicy ) , '不吃辣:' , JSON . stringify ( m . nonSpicy ) ) ;
} ,
/** 查询部门报餐列表 */
getList ( ) {
this . loading = true ;
@@ -209,53 +354,23 @@
} ) ;
} ,
/** 核心逻辑:区分有效/无效报餐统计 */
calcTableSum ( ) {
let validDine = 0 ,
validTake = 0 ,
validAll = 0 ;
let invalidDine = 0 ,
invalidTake = 0 ,
invalidAll = 0 ;
this . list . forEach ( item => {
// 处理空值,转为数字
const dine = item . dineInPeople ? Number ( item . dineInPeople ) : 0 ;
const take = item . takeoutPeople ? Number ( item . takeoutPeople ) : 0 ;
const total = item . totalPeople ? Number ( item . totalPeople ) : 0 ;
// 判断当前报餐是否有效
if ( this . isValidMealReport ( item . createTime ) ) {
validDine += dine ;
validTake += take ;
validAll += total ;
} else {
invalidDine += dine ;
invalidTake += take ;
invalidAll += total ;
}
} ) ;
// 赋值到统计变量
this . validDineIn = validDine ;
this . validTakeout = validTake ;
this . validTotal = validAll ;
this . invalidDineIn = invalidDine ;
this . invalidTakeout = invalidTake ;
this . invalidTotal = invalidAll ;
/** 判断报餐是否有效: 仅比较时分秒( 参考PC端逻辑) */
isValidMealReport ( createTime ) {
if ( ! createTime || ! this . queryParams . deadlineTime ) return true ; // 无时间默认有效
// 提取报餐时间的时分秒
const reportTime = new Date ( createTime ) ;
const reportHms = this . addZero ( reportTime . getHours ( ) ) + ':' +
this . addZero ( reportTime . getMinutes ( ) ) + ':' +
this . addZero ( reportTime . getSeconds ( ) ) ;
// 比较时分秒字符串( 格式HH:mm:ss, 可直接字符串比较)
return reportHms < = this . queryParams . deadlineTime ;
} ,
/** 判断报餐是否有效:创建时间在截止时间之前则有效 */
isValidMealReport ( createTime ) {
if ( ! createTime ) return false ;
// 拼接完整的截止时间字符串
const deadlineDateTime = ` ${ this . deadlineDate || this . queryParams . reportDate } ${ this . deadlineTime } ` ;
// 比较时间
const createTimeObj = new Date ( createTime ) ;
const deadlineTimeObj = new Date ( deadlineDateTime ) ;
return createTimeObj <= deadlineTimeObj ;
/** 数字补零 */
addZero ( num ) {
return num < 10 ? '0' + num : num ;
} ,
/** 报餐日期选择确认 */
@@ -307,8 +422,9 @@
const hour = String ( this . timeSelect . hour ) . padStart ( 2 , '0' ) ;
const minute = String ( this . timeSelect . minute ) . padStart ( 2 , '0' ) ;
const second = String ( this . timeSelect . second ) . padStart ( 2 , '0' ) ;
// 更新截止时间
// 更新截止时间(同步到 queryParams 供有效性判断使用)
this . deadlineTime = ` ${ hour } : ${ minute } : ${ second } ` ;
this . queryParams . deadlineTime = ` ${ hour } : ${ minute } : ${ second } ` ;
// 重新计算统计数据
this . calcTableSum ( ) ;
// 关闭弹窗
@@ -394,6 +510,14 @@
border - top : none ;
}
/* 交叉表网格: 4列( 标签 + 堂食 + 打包 + 小计) */
. cross - grid {
display : grid ;
grid - template - columns : 70 px repeat ( 3 , 1 fr ) ;
border : 1 px solid # eee ;
border - top : none ;
}
/* 统计项样式 */
. stats - item {
padding : 12 px 8 px ;
@@ -404,18 +528,54 @@
font - size : 14 px ;
}
/* 去掉最后一列右边框 */
/* 3列网格: 去掉最后一列右边框 */
. stats - grid . stats - item : nth - child ( 3 n ) {
border - right : none ;
}
/* 去掉最后一行下边框 */
/* 3列网格: 去掉最后一行下边框 */
. stats - grid . stats - item : last - child ,
. stats - grid . stats - item : nth - last - child ( 2 ) ,
. stats - grid . stats - item : nth - last - child ( 3 ) {
border - bottom : none ;
}
/* 4列交叉网格: 去掉最后一列右边框 */
. cross - grid . stats - item : nth - child ( 4 n ) {
border - right : none ;
}
/* 4列交叉网格: 去掉最后一行下边框( 每行4个, 最后4个) */
. cross - grid . stats - item : nth - last - child ( - n + 4 ) {
border - bottom : none ;
}
/* 交叉表行标签(吃辣/不吃辣/小计) */
. cross - label {
display : flex ;
align - items : center ;
justify - content : center ;
gap : 3 px ;
font - weight : 500 ;
color : # 333 ;
font - size : 13 px ;
background - color : # fafafa ;
}
/* 交叉表头(堂食/打包/小计) */
. cross - header {
font - weight : 600 ;
color : # 555 ;
font - size : 12 px ;
background - color : # f5f7fa ;
}
/* 交叉表汇总行背景 */
. cross - total {
background - color : # f9f9f9 ;
font - weight : 600 ;
}
/* 标签和值样式 */
. item - label {
display : block ;
@@ -496,12 +656,6 @@
color : # 999 ;
}
. colon {
font - size : 20 px ;
color : # 333 ;
margin : 0 5 px ;
}
/* 适配小屏手机 */
@ media ( max - width : 375 px ) {
. stats - item {