620 lines
15 KiB
Vue
620 lines
15 KiB
Vue
<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> |