baodeng_xcx/page_shanghu/paidui/shhxlist.vue

620 lines
15 KiB
Vue
Raw Normal View History

2025-08-19 17:02:49 +08:00
<template>
<view class="page">
<u-navbar title="叫号控制" :border-bottom="false" :background="bgc" back-icon-color="#fff" title-color='#fff'
title-size='36' height='36' id="navbar">
</u-navbar>
<view class="content">
<!-- 头部区域 -->
<view class="header">
<view class="date-section" @click="show = true">
<view class="date">{{ currentDate }}</view>
<view class="calendar-icon"><image src="https://api.ccttiot.com/smartmeter/img/static/uIKD9zzjb0jlIWmHrtpz" mode=""></image> </view>
</view>
<view class="search-section">
<input
class="search-input"
placeholder="输入关键字查询"
v-model="searchKeyword"
placeholder-class="search-placeholder"
/>
<view class="search-icon"><image src="https://api.ccttiot.com/smartmeter/img/static/urVMNY8rV4QlGT923KEo" mode=""></image> </view>
</view>
</view>
<!-- 统计信息 -->
<view class="statistics">
<view class="stat-text">排队{{ tjobj.pending == undefined ? '--' : tjobj.pending}} 叫号{{ tjobj.calling == undefined ? '--' : tjobj.calling }} 过号{{ tjobj.passed == undefined ? '--' : tjobj.passed }} 核销{{ tjobj.writtenOff == undefined ? '--' : tjobj.writtenOff }} 全部{{ tjobj. totalCount == undefined ? '--' : tjobj. totalCount }}</view>
</view>
<!-- 导航标签 -->
<view class="nav-tabs">
<view
v-for="(tab, index) in tabs"
:key="index"
class="tab-item"
:class="{ active: activeTab === tab.key }"
@click="switchTab(tab.key)"
>
<view class="tab-text">{{ tab.name }}</view>
</view>
</view>
<!-- 队列列表 -->
<view class="queue-list">
<view v-if="loading" style="color:#808080;font-size:28rpx;padding:20rpx;">加载中...</view>
<view v-else-if="!queueList.length" style="color:#808080;font-size:28rpx;padding:20rpx;">
{{ errorMessage || '暂无数据' }}
</view>
<view
v-for="(item, index) in queueList"
:key="index"
class="queue-item"
>
<view class="item-main">
<view class="item-left">
<view class="item-id">{{ item.number }}</view>
<view class="item-time">取号时间: {{ item.createTime }}</view>
</view>
<view class="item-right">
<view class="user-name">{{ item.nickName }}</view>
<view class="status-badge" :class="'status-' + normalizeStatus(item.status)">
<view class="status-text">{{ getStatusText(item.status) }}</view>
</view>
</view>
</view>
<!-- 分隔线 -->
<view class="separator-line"></view>
<view class="item-actions">
<view class="action-buttons">
<view
v-for="(action, actionIndex) in getAvailableActions(item.status)"
:key="actionIndex"
class="action-btn"
:class="action.type"
@click="handleAction(action,item)"
>
{{ action.text }}
</view>
</view>
</view>
</view>
</view>
<u-picker mode="time" v-model="show" :params="params" @confirm="btntime"></u-picker>
</view>
</view>
</template>
<script>
export default {
data() {
return {
params: {
year: true,
month: true,
day: true,
hour: false,
minute: false,
second: false
},
show: false,
bgc: {
backgroundColor: "#000",
},
currentDate: '',
searchKeyword: '',
activeTab: 'all',
tabs: [
{ key: '1', name: '待叫号' },
{ key: '2', name: '叫号中' },
{ key: '4', name: '已过号' },
{ key: '3', name: '已核销' },
{ key: 'all', name: '全部' }
],
status:'',
queueList: [],
allList: [],
stats: { inQueue: 0, calling: 0, missed: 0, verified: 0, total: 0 },
loading: false,
errorMessage: '',
tjobj:{}
}
},
onLoad(option) {
},
onShow() {
const now = new Date();
// 自动补零的格式化函数
function formatDate(date, separator = '-', needPadZero = true) {
const year = date.getFullYear();
const month = needPadZero
? String(date.getMonth() + 1).padStart(2, '0')
: date.getMonth() + 1;
const day = needPadZero
? String(date.getDate()).padStart(2, '0')
: date.getDate();
return `${year}${separator}${month}${separator}${day}`;
}
// 使用示例
const dateStr1 = formatDate(now);
this.currentDate = dateStr1
this.getlist()
this.gettongji()
},
methods: {
btntime(e){
console.log(e);
this.currentDate = e.year + '-' + e.month + '-' + e.day
this.searchKeyword = ''
this.getlist()
this.gettongji()
},
// 获取所有统计
gettongji(){
this.$u.get(`/bst/queueUser/selectCount?storeId=${this.$store.state.storeId}&timeRange=${this.currentDate + ',' + this.currentDate}`).then(res =>{
if(res.code == 200){
this.tjobj = res.data
}
})
},
// 规范状态为统一的 key
normalizeStatus(status) {
if (status === 1 || status === '1') return 'inQueue'
if (status === 2 || status === '2') return 'calling'
if (status === 3 || status === '3') return 'verified'
if (status === 4 || status === '4') return 'missed'
if (status === 5 || status === '5') return 'cancelled'
return status
},
// 计算统计
updateStats(list) {
const counters = { inQueue: 0, calling: 0, missed: 0, verified: 0, total: 0 }
(list || []).forEach(item => {
const key = this.normalizeStatus(item.status)
if (Object.prototype.hasOwnProperty.call(counters, key)) counters[key]++
counters.total++
})
this.stats = counters
},
// 本地过滤(按标签与关键字)
getFilteredList(list) {
const keyword = (this.searchKeyword || '').trim()
return (list || []).filter(item => {
const matchTab = this.activeTab === 'all' || this.normalizeStatus(item.status) === this.normalizeStatus(this.activeTab)
const text = `${item.number || ''} ${item.nickName || ''}`
const matchKeyword = !keyword || text.indexOf(keyword) !== -1
return matchTab && matchKeyword
})
},
// 获取排队信息
getlist(){
this.loading = true
this.errorMessage = ''
const storeId = this.$store && this.$store.state ? this.$store.state.storeId : ''
if(!storeId){
uni.showToast({ title:'缺少门店信息', icon:'none' })
this.allList = []
this.queueList = []
this.updateStats([])
this.loading = false
return
}
const query = [`storeId=${storeId}`]
if (this.status) query.push(`status=${this.status}`)
this.$u.get(`/bst/queueUser/list?storeId=${storeId}&status=${this.status}&pageNum=1&pageSize=999&orderByColumn=createTime&isAsc=desc&timeRange=${this.currentDate + ',' + this.currentDate}`).then(res =>{
if(res.code == 200){
this.queueList = res.rows
this.allList = res.rows
}else{
this.allList = []
this.queueList = []
this.errorMessage = (res && res.msg) ? res.msg : '加载失败'
}
}).catch((e)=>{
this.allList = []
this.queueList = []
this.errorMessage = '网络错误'
}).finally(()=>{ this.loading = false })
},
refreshList(){ this.getlist() },
// 通用队列操作:尝试多个可能的后端端点
switchTab(tabKey) {
this.activeTab = tabKey
this.status = tabKey === 'all' ? '' : tabKey
// 切换标签时重新拉取数据,避免后端过滤和本地过滤不一致
this.searchKeyword = ''
this.getlist()
},
showDatePicker() {
// 兜底处理:先不弹日期选择,避免环境不支持时报错
uni.showToast({ title: '日期筛选暂未开通', icon: 'none' })
},
loadDataByDate(date) {
// 根据选择的日期加载数据
console.log('加载日期数据:', date);
// 这里可以调用API获取指定日期的数据
// 例如this.fetchQueueData(date);
},
getStatusText(status) {
const statusTexts = {
'inQueue': '待叫号',
'calling': '叫号中',
'missed': '已过号',
'verified': '已核销',
'cancelled': '已取消'
};
const key = this.normalizeStatus(status)
return statusTexts[key] || '';
},
getAvailableActions(status) {
const actionMap = {
'inQueue': [
{ text: '叫号', action: 'call', type: 'primary' },
{ text: '过号', action: 'miss', type: 'primary' },
{ text: '取消', action: 'cancel', type: 'secondary' },
{ text: '核销', action: 'verify', type: 'primary' }
],
'calling': [
{ text: '过号', action: 'miss', type: 'primary' },
{ text: '取消', action: 'cancel', type: 'secondary' },
{ text: '核销', action: 'verify', type: 'primary' }
],
'missed': [
{ text: '恢复叫号', action: 'restore', type: 'primary' },
{ text: '取消', action: 'cancel', type: 'secondary' }
],
'verified': [
// { text: '恢复排队', action: 'restore', type: 'primary' },
// { text: '取消', action: 'cancel', type: 'secondary' }
]
};
const key = this.normalizeStatus(status)
return actionMap[key] || [];
},
// 点击操作
handleAction(action,item) {
console.log(action,item);
let that = this
uni.showModal({
title: '提示',
content: `您确定要执行${action.text}操作吗?`,
success: (res) => {
if (res.confirm) {
if(action.text == '叫号' || action.text == '恢复叫号'){
let data = {
queueUserId:item.id
}
that.$u.post(`/bst/queueUser/calling`,data).then(res =>{
if(res.code == 200){
uni.showToast({ title:'叫号成功', icon:'success',duration:3000})
that.getlist()
}else{
uni.showToast({ title:res.msg, icon:'none',duration:3000})
}
})
}else if(action.text == '过号'){
let data = [item.id]
that.$u.post(`/bst/queueUser/pass`,data).then(res =>{
if(res.code == 200){
uni.showToast({ title:'过号成功', icon:'success',duration:3000})
that.getlist()
}else{
uni.showToast({ title:res.msg, icon:'none',duration:3000})
}
})
}else if(action.text == '取消'){
let data = [item.id]
that.$u.post(`/bst/queueUser/cancelQueue`,data).then(res =>{
if(res.code == 200){
uni.showToast({ title:'取消成功', icon:'success',duration:3000})
that.getlist()
}else{
uni.showToast({ title:res.msg, icon:'none',duration:3000})
}
})
}else if(action.text == '核销'){
that.$u.post(`/app/queueUser/generateCode/${item.id}`).then(res => {
if (res.code == 200) {
let data = {
id:item.id,
verificationCode:res.data.verificationCode
}
that.$u.post(`/bst/queueUser/verification`,data).then(res =>{
if(res.code == 200){
uni.showToast({ title:'核销成功', icon:'success',duration:3000})
that.getlist()
}else{
uni.showToast({ title:res.msg, icon:'none',duration:3000})
}
})
}else{
uni.showToast({ title:res.msg, icon:'success',duration:3000})
}
})
}
}
}
})
},
},
watch:{
searchKeyword(){
this.queueList = this.getFilteredList(this.allList)
}
}
}
</script>
<style lang="scss">
page {
background: #0c0c0c;
}
.page {
min-height: 100vh;
background: #0c0c0c;
}
.content {
padding: 30rpx;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40rpx;
}
.date-section {
display: flex;
align-items: center;
gap: 8rpx;
cursor: pointer;
padding: 10rpx;
border-radius: 8rpx;
transition: background-color 0.3s;
&:active {
background-color: rgba(255, 255, 255, 0.1);
}
.date {
color: #fff;
font-size: 36rpx;
font-weight: 500;
}
.calendar-icon {
image{
width: 20rpx;
height: 20rpx;
}
}
}
.search-section {
position: relative;
flex: 1;
margin-left: 40rpx;
.search-input {
width: 300rpx;
height: 80rpx;
background: #1a1a1a;
border: 1px solid #333;
border-radius: 10rpx;
padding: 0 70rpx 0 35rpx;
color: #fff;
font-size: 30rpx;
}
.search-placeholder {
color: #999;
font-size: 30rpx;
}
.search-icon {
position: absolute;
right: 64rpx;
top: 50%;
transform: translateY(-50%);
font-size: 28rpx;
color: #fff;
image{
width: 26rpx;
height: 26rpx;
}
}
}
.refresh-btn{
margin-left: 20rpx;
padding: 12rpx 20rpx;
border-radius: 10rpx;
font-size: 24rpx;
background: #FF8998;
color: #fff;
border: 1px solid #FF8998;
}
.statistics {
padding: 20rpx 0;
.stat-text {
color: #808080;
font-size: 30rpx;
line-height: 1.5;
}
}
.nav-tabs {
display: flex;
margin-bottom: 40rpx;
border-bottom: 1px solid #333;
.tab-item {
flex: 1;
text-align: center;
padding: 25rpx 0;
position: relative;
.tab-text {
color: #fff;
font-size: 30rpx;
}
&.active {
.tab-text {
color: #fff;
}
&::after {
content: '';
position: absolute;
bottom: -1px;
left: 50%;
transform: translateX(-50%);
width: 80rpx;
height: 6rpx;
background: #FF8998;
border-radius: 3rpx;
}
}
}
}
.queue-list {
.queue-item {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 20rpx;
.item-main {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20rpx;
.item-left {
display: flex;
flex-direction: column;
gap: 12rpx;
.item-id {
color: #fff;
font-size: 64rpx;
font-weight: bold;
line-height: 1;
}
.item-time {
color: #999;
font-size: 24rpx;
}
}
.item-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 12rpx;
.user-name {
color: #fff;
font-size: 28rpx;
}
.status-badge {
padding: 6rpx 16rpx;
border-radius: 10rpx;
font-size: 22rpx;
&.status-inQueue {
background: #8883F0;
color: #fff;
}
&.status-calling {
background: #FF8998;
color: #fff;
}
&.status-missed {
background: #fff;
color: #000;
}
&.status-verified {
background: #FF8998;
color: #fff;
}
&.status-cancelled {
background: #666;
color: #fff;
}
}
}
}
.separator-line {
height: 1px;
background: #333;
margin: 20rpx 0;
}
.item-actions {
display: flex;
justify-content: space-between;
align-items: center;
.action-buttons {
display: flex;
gap: 15rpx;
.action-btn {
padding: 12rpx 20rpx;
border-radius: 10rpx;
font-size: 24rpx;
border: none;
min-width: 100rpx;
cursor: pointer;
text-align: center;
&.primary {
background: #FF8998;
color: #fff;
}
&.secondary {
background: transparent;
color: #FF8998;
border: 1px solid #FF8998;
}
}
}
.details-btn {
padding: 12rpx 20rpx;
border-radius: 20rpx;
font-size: 24rpx;
background: #fff;
color: #000;
border: 1px solid #FF8998;
min-width: 100rpx;
cursor: pointer;
text-align: center;
}
}
}
}
</style>