Merge branch 'master' of https://codeup.aliyun.com/bsdtec/biz-mobile-react-cloud
This commit is contained in:
commit
741292505a
6 files changed
+815
-103
No files matched your search
@@ -19,18 +19,16 @@ VITE_OAUTH2_APP_CLIENT='mini:mini'
|
||||
# --------------------------- 开发环境 api地址 ----------------------------------
|
||||
# VITE_APP_DEV_SERVICE = https://stgl.pj.gov.cn/api # 域名线上
|
||||
#VITE_APP_DEV_SERVICE = http://59.202.170.152:81/api # 信创线上
|
||||
# VITE_APP_DEV_SERVICE = http://192.168.101.139:9999 # 邓-本地
|
||||
# VITE_APP_DEV_SERVICE = http://127.0.0.1:9999 # 邓-本地
|
||||
# VITE_APP_DEV_SERVICE = http://123.156.230.15:8089/api # 测试线上-过期
|
||||
VITE_APP_DEV_SERVICE = http://123.157.250.162:8089/api # 测试线上
|
||||
#VITE_APP_DEV_SERVICE = http://192.168.101.139:9999 # 邓-本地
|
||||
VITE_APP_DEV_SERVICE = http://127.0.0.1:9999 # 邓-本地
|
||||
# VITE_APP_DEV_SERVICE = http://123.156.230.15:8089/api # 测试线上
|
||||
|
||||
# --------------------------- 开发环境 图片访问基础地址 ---------------------------
|
||||
# VITE_IMAGE_DEV_PATH = https://stgl.pj.gov.cn/api # 域名线上
|
||||
#VITE_IMAGE_DEV_PATH = http://59.202.170.152:81/api # 信创线上
|
||||
# VITE_IMAGE_DEV_PATH = http://192.168.101.139:9999 # 邓-本地
|
||||
# VITE_IMAGE_DEV_PATH = http://127.0.0.1:9999
|
||||
# VITE_IMAGE_DEV_PATH = http://123.156.230.15:8089/api # 测试线上-过期
|
||||
VITE_IMAGE_DEV_PATH = http://123.157.250.162:8089/api # 测试线上
|
||||
#VITE_IMAGE_DEV_PATH = http://192.168.101.139:9999 # 邓-本地
|
||||
VITE_IMAGE_DEV_PATH = http://127.0.0.1:9999
|
||||
# VITE_IMAGE_DEV_PATH = http://123.156.230.15:8089/api # 测试线上
|
||||
|
||||
# --------------------------- 生产环境 api地址 ----------------------------------
|
||||
VITE_APP_PROD_SERVICE = https://stgl.pj.gov.cn/api # 域名线上
|
||||
|
||||
+8
-8
@@ -544,13 +544,13 @@
|
||||
"iconPath": "",
|
||||
"selectedIconPath": ""
|
||||
}
|
||||
// ,
|
||||
// {
|
||||
// "pagePath": "pages/integrated/index",
|
||||
// "text": "综合服务",
|
||||
// "iconPath": "static/tabbar/integrated.png",
|
||||
// "selectedIconPath": "static/tabbar/integrated-select.png"
|
||||
// }
|
||||
,
|
||||
{
|
||||
"pagePath": "pages/integrated/index",
|
||||
"text": "综合服务",
|
||||
"iconPath": "static/tabbar/integrated.png",
|
||||
"selectedIconPath": "static/tabbar/integrated-select.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,30 +22,34 @@
|
||||
|
||||
<!-- 日期切换 -->
|
||||
<view class="date-tabs">
|
||||
<view v-for="(d, i) in dateTabs" :key="i" :class="['date-tab', i === activeDate ? 'active' : '']" @tap="activeDate = i">{{ d }}</view>
|
||||
<view v-for="(d, i) in dateTabs" :key="i" :class="['date-tab', i === activeDate ? 'active' : '']" @tap="handleDateChange(i)">{{ d }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 时间选择表格 -->
|
||||
<view class="time-table">
|
||||
<view class="time-row" v-for="(row, rowIdx) in timeTable" :key="rowIdx">
|
||||
<view class="time-cell" v-for="(cell, colIdx) in row" :key="colIdx"
|
||||
:class="cell.status"
|
||||
@tap="selectTime(rowIdx, colIdx)"
|
||||
>
|
||||
<text v-if="cell.status === 'booked'">{{ cell.user }}</text>
|
||||
<text v-else-if="cell.status === 'disabled'">🚫</text>
|
||||
<!-- 时间选择列表 -->
|
||||
<view class="time-list-container">
|
||||
<scroll-view class="time-list" scroll-y>
|
||||
<view class="time-list-content">
|
||||
<view v-for="(slot, index) in timeSlots" :key="index"
|
||||
class="time-item"
|
||||
:class="[slot.status, selectedTime.includes(index) ? 'selected' : '']"
|
||||
@tap="selectTime(index)"
|
||||
>
|
||||
<view class="time-range">{{ slot.startTime }} - {{ slot.endTime }}</view>
|
||||
<view class="time-status">
|
||||
<text v-if="slot.status === 'booked'">🚫 已预约: {{ slot.bookedBy?.userName }}</text>
|
||||
<text v-else-if="slot.status === 'disabled'">🚫 不可预约</text>
|
||||
<text v-else>✓ 可预约</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="time-labels">
|
||||
<text v-for="h in 10" :key="h" class="time-label">{{ 8 + h }}:00</text>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 预约信息 -->
|
||||
<view class="reserve-info">
|
||||
<view>会议时间:{{ selectedTimeText }}</view>
|
||||
<view>时长:{{ selectedDuration }}分钟</view>
|
||||
<view>发起人:张三</view>
|
||||
<view>发起人:{{ currentUser.name }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 预约按钮 -->
|
||||
@@ -54,62 +58,249 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import meetingRoomApi from '@/sheep/api/meetingRoom'
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
// 定义类型
|
||||
interface TimeSlotItem {
|
||||
startTime: string
|
||||
endTime: string
|
||||
status: 'free' | 'booked' | 'disabled'
|
||||
bookedBy: {
|
||||
userId: string
|
||||
userName: string
|
||||
} | null
|
||||
}
|
||||
|
||||
// 假数据,可替换为实际接口
|
||||
interface ApiResponse<T> {
|
||||
code: number
|
||||
msg: string | null
|
||||
data: T
|
||||
ok: boolean
|
||||
}
|
||||
|
||||
// 会议室ID
|
||||
const roomId = ref('')
|
||||
|
||||
// 获取页面参数
|
||||
const props = defineProps<{
|
||||
roomId?: string
|
||||
roomInfo?: any
|
||||
}>()
|
||||
|
||||
// 使用传入的会议室信息,如果没有则使用默认值
|
||||
const room = ref({
|
||||
name: '一号会议室',
|
||||
capacity: 20,
|
||||
address: '公司总部南水吧台旁',
|
||||
tags: ['电视', '白板', '投影仪'],
|
||||
img: '/static/meeting/room1.jpg',
|
||||
img: '/static/meeting/room1.jpg'
|
||||
})
|
||||
|
||||
const dateTabs = ['今天', '明天', '后天', '选择日期']
|
||||
// 当前用户信息
|
||||
const currentUser = ref({
|
||||
name: '张三',
|
||||
id: '1'
|
||||
})
|
||||
|
||||
// 日期选项
|
||||
const dateTabs = ['今天', '明天', '后天', '大后天']
|
||||
const activeDate = ref(0)
|
||||
|
||||
// 时间表格(4行10列,每格15分钟)
|
||||
const timeTable = ref([
|
||||
[
|
||||
{ status: 'disabled' }, { status: 'disabled' }, { status: 'booked', user: '张三' }, { status: 'booked', user: '张三' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }
|
||||
],
|
||||
[
|
||||
{ status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'booked', user: '张三' }, { status: 'booked', user: '张三' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }
|
||||
],
|
||||
[
|
||||
{ status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }
|
||||
],
|
||||
[
|
||||
{ status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }
|
||||
]
|
||||
])
|
||||
// 时间段数据
|
||||
const timeSlots = ref<TimeSlotItem[]>([])
|
||||
|
||||
const selectedTime = ref<{row: number, col: number} | null>(null)
|
||||
// 选中的时间
|
||||
const selectedTime = ref<number[]>([])
|
||||
const selectedTimeText = computed(() => {
|
||||
if (!selectedTime.value) return '未选择'
|
||||
const start = 8 + selectedTime.value.col * 0.5
|
||||
const end = start + 0.5
|
||||
return `${Math.floor(start)}:${start % 1 === 0 ? '00' : '30'}~${Math.floor(end)}:${end % 1 === 0 ? '00' : '30'}`
|
||||
if (selectedTime.value.length === 0) return '未选择'
|
||||
const start = timeSlots.value[selectedTime.value[0]]
|
||||
const end = timeSlots.value[selectedTime.value[selectedTime.value.length - 1]]
|
||||
return `${start.startTime} - ${end.endTime}`
|
||||
})
|
||||
const selectedDuration = computed(() => 30)
|
||||
const selectedDuration = computed(() => selectedTime.value.length * 30)
|
||||
|
||||
function selectTime(row: number, col: number) {
|
||||
const cell = timeTable.value[row][col]
|
||||
if (cell.status === 'free') {
|
||||
selectedTime.value = { row, col }
|
||||
// 获取日期字符串
|
||||
const getDateString = (index: number) => {
|
||||
const date = new Date()
|
||||
date.setDate(date.getDate() + index)
|
||||
return date.toISOString().split('T')[0]
|
||||
}
|
||||
|
||||
// 处理日期切换
|
||||
const handleDateChange = async (index: number) => {
|
||||
activeDate.value = index
|
||||
const date = getDateString(index)
|
||||
await fetchTimeSlots(date)
|
||||
}
|
||||
|
||||
// 获取时间段数据
|
||||
const fetchTimeSlots = async (date: string) => {
|
||||
if (!roomId.value) {
|
||||
console.log('No roomId available for API request')
|
||||
uni.showToast({
|
||||
title: '会议室ID不存在',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
console.log('Making API request with roomId:', roomId.value)
|
||||
try {
|
||||
const res = await meetingRoomApi.getTimeSlots(roomId.value, date)
|
||||
console.log('Raw API response:', res)
|
||||
|
||||
const response = res as unknown as ApiResponse<TimeSlotItem[]>
|
||||
console.log('Parsed response:', response)
|
||||
|
||||
if (response.code === 0 && response.data) {
|
||||
console.log('Time slots data:', response.data)
|
||||
timeSlots.value = response.data
|
||||
} else {
|
||||
console.error('Invalid response:', response)
|
||||
uni.showToast({
|
||||
title: response.msg || '获取时间段失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('API request failed:', error)
|
||||
uni.showToast({
|
||||
title: '获取时间段失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function reserveMeeting() {
|
||||
if (!selectedTime.value) {
|
||||
// 选择时间
|
||||
function selectTime(index: number) {
|
||||
const slot = timeSlots.value[index]
|
||||
if (slot.status === 'free') {
|
||||
// 检查是否已经选中
|
||||
const currentIndex = selectedTime.value.indexOf(index)
|
||||
if (currentIndex === -1) {
|
||||
// 检查是否连续
|
||||
if (selectedTime.value.length === 0) {
|
||||
selectedTime.value.push(index)
|
||||
} else {
|
||||
const last = selectedTime.value[selectedTime.value.length - 1]
|
||||
if (last === index - 1) {
|
||||
selectedTime.value.push(index)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '请选择连续的时间段',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 取消选中
|
||||
selectedTime.value.splice(currentIndex, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 预约会议
|
||||
async function reserveMeeting() {
|
||||
if (selectedTime.value.length === 0) {
|
||||
uni.showToast({ title: '请选择时间', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '预约成功', icon: 'success' })
|
||||
|
||||
if (!roomId.value) {
|
||||
uni.showToast({ title: '会议室ID不存在', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const date = getDateString(activeDate.value)
|
||||
const startSlot = timeSlots.value[selectedTime.value[0]]
|
||||
const endSlot = timeSlots.value[selectedTime.value[selectedTime.value.length - 1]]
|
||||
|
||||
const res = await meetingRoomApi.bookMeetingRoom(roomId.value, {
|
||||
date,
|
||||
startTime: startSlot.startTime,
|
||||
endTime: endSlot.endTime,
|
||||
title: '会议',
|
||||
description: '',
|
||||
attendees: []
|
||||
})
|
||||
|
||||
const response = res as unknown as ApiResponse<any>
|
||||
if (response.code === 0) {
|
||||
uni.showToast({ title: '预约成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: response.msg || '预约失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: '预约失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
onMounted(() => {
|
||||
console.log('Detail page mounted, props:', props)
|
||||
console.log('roomId from props:', props.roomId)
|
||||
|
||||
// 从props获取roomId
|
||||
if (props.roomId) {
|
||||
roomId.value = props.roomId
|
||||
console.log('roomId set to:', roomId.value)
|
||||
} else {
|
||||
console.log('No roomId found in props')
|
||||
}
|
||||
|
||||
// 接收列表页传递的会议室信息
|
||||
const currentPage = getCurrentPages().pop()
|
||||
const eventChannel = currentPage ? (currentPage as any).getOpenerEventChannel() : undefined
|
||||
if (eventChannel) {
|
||||
eventChannel.on('acceptRoomInfo', (data: any) => {
|
||||
console.log('detail.vue: Received raw room info from eventChannel:', data)
|
||||
// 字段映射
|
||||
room.value.name = data.roomName || '未知会议室'
|
||||
room.value.capacity = data.capacity || 0
|
||||
room.value.address = data.location || '未知地址'
|
||||
room.value.img = data.picUrl || '/static/meeting/default.jpg'
|
||||
room.value.tags = data.equipmentNames ? data.equipmentNames.map((eq: { name: string, quantity?: number }) => eq.quantity ? `${eq.name} x${eq.quantity}` : eq.name) : []
|
||||
console.log('detail.vue: Room.value after mapping:', room.value)
|
||||
})
|
||||
}
|
||||
|
||||
// 获取当天的时间段
|
||||
const today = getDateString(0)
|
||||
console.log('Fetching time slots for date:', today)
|
||||
fetchTimeSlots(today)
|
||||
|
||||
// 从 localStorage 获取用户信息
|
||||
try {
|
||||
const userInfoString = localStorage.getItem('user_info')
|
||||
if (userInfoString) {
|
||||
const userInfoData = JSON.parse(userInfoString)
|
||||
if (userInfoData && userInfoData.data && userInfoData.data.sysUser && userInfoData.data.sysUser.username) {
|
||||
currentUser.value.name = userInfoData.data.sysUser.username
|
||||
if (userInfoData.data.sysUser.userId) {
|
||||
currentUser.value.id = userInfoData.data.sysUser.userId.toString()
|
||||
}
|
||||
console.log('detail.vue: Current user name set from localStorage:', currentUser.value.name)
|
||||
console.log('detail.vue: Current user ID set from localStorage:', currentUser.value.id)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('detail.vue: Failed to parse user_info from localStorage:', e)
|
||||
}
|
||||
})
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -117,6 +308,8 @@ function reserveMeeting() {
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background: #f5f6fa;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 32rpx;
|
||||
}
|
||||
.custom-nav {
|
||||
@@ -220,58 +413,62 @@ function reserveMeeting() {
|
||||
background: #e6f0ff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.time-table {
|
||||
.time-list-container {
|
||||
margin: 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.time-list {
|
||||
flex: 1;
|
||||
}
|
||||
.time-list-content {
|
||||
padding: 16rpx;
|
||||
}
|
||||
.time-row {
|
||||
display: flex;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.time-cell {
|
||||
flex: 1;
|
||||
height: 48rpx;
|
||||
margin-right: 4rpx;
|
||||
border-radius: 6rpx;
|
||||
background: #e6eaf2;
|
||||
.time-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #bbb;
|
||||
font-size: 22rpx;
|
||||
padding: 24rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
background: #f8f9fc;
|
||||
margin-bottom: 16rpx;
|
||||
border-radius: 8rpx;
|
||||
position: relative;
|
||||
}
|
||||
.time-cell:last-child {
|
||||
margin-right: 0;
|
||||
.time-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.time-cell.free {
|
||||
background: #4d8fff;
|
||||
color: #fff;
|
||||
.time-item.free {
|
||||
background: #e6f0ff;
|
||||
color: #1777FF;
|
||||
}
|
||||
.time-cell.booked {
|
||||
background: #ffb74d;
|
||||
color: #fff;
|
||||
.time-item.booked {
|
||||
background: #fff3e0;
|
||||
color: #ff9800;
|
||||
}
|
||||
.time-cell.disabled {
|
||||
background: #f0f0f0;
|
||||
color: #ccc;
|
||||
.time-item.disabled {
|
||||
background: #f5f5f5;
|
||||
color: #999;
|
||||
}
|
||||
.time-cell.selected {
|
||||
.time-item.selected {
|
||||
border: 2rpx solid #1777FF;
|
||||
}
|
||||
.time-labels {
|
||||
display: flex;
|
||||
margin: 0 24rpx 16rpx 24rpx;
|
||||
.time-range {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
.time-label {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
color: #bbb;
|
||||
font-size: 20rpx;
|
||||
.time-status {
|
||||
font-size: 24rpx;
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 4rpx;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.reserve-info {
|
||||
margin: 0 24rpx 16rpx 24rpx;
|
||||
margin: 0 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx;
|
||||
@@ -279,7 +476,7 @@ function reserveMeeting() {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.reserve-btn {
|
||||
margin: 0 24rpx;
|
||||
margin: 24rpx;
|
||||
background: linear-gradient(90deg, #0094ff, #06be36);
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
@@ -287,6 +484,5 @@ function reserveMeeting() {
|
||||
border-radius: 40rpx;
|
||||
padding: 24rpx 0;
|
||||
font-weight: 600;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -74,8 +74,15 @@ function fetchMeetingRooms() {
|
||||
}
|
||||
|
||||
function goDetail(room) {
|
||||
console.log('index.vue: Navigating to detail page with room:', room)
|
||||
console.log('index.vue: Room ID being passed:', room.id)
|
||||
uni.navigateTo({
|
||||
url: `/pages/integrated/meeting/detail?roomId=${room.id}`
|
||||
url: `/pages/integrated/meeting/detail?roomId=${room.id}`,
|
||||
success: function(res) {
|
||||
// 通过 eventChannel 传递 room 对象
|
||||
res.eventChannel.emit('acceptRoomInfo', room)
|
||||
console.log('index.vue: Room object emitted via eventChannel:', room)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,484 @@
|
||||
<template>
|
||||
<view class="visitor-list">
|
||||
<!-- 顶部标签页 -->
|
||||
<view class="tabs">
|
||||
<view v-for="(tab, index) in tabs" :key="index" class="tab-item" :class="{ active: currentTab === index }" @tap="switchTab(index)">
|
||||
<text>{{ tab }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 访客记录列表 -->
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="list-content"
|
||||
:refresher-enabled="true"
|
||||
:refresher-triggered="refreshing"
|
||||
@refresherrefresh="onRefresh"
|
||||
@scrolltolower="loadMore"
|
||||
>
|
||||
<view v-for="item in visitorList" :key="item.id" class="visitor-item" @tap="goToDetail(item)">
|
||||
<view class="item-header">
|
||||
<text class="visit-time">{{ formatTime(item.vstartDate, item.vendDate) }}</text>
|
||||
<text :class="['status-tag', getStatusClass(item.checkStatus)]">
|
||||
{{ getStatusText(item.checkStatus) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="item-content">
|
||||
<view class="info-row">
|
||||
<text class="label">接待人:</text>
|
||||
<text class="value">{{ item.userName }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">联系方式:</text>
|
||||
<text class="value">{{ item.mobile }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">预约时间:</text>
|
||||
<text class="value">{{ formatCreateTime(item.createTime) }}</text>
|
||||
</view>
|
||||
<view v-if="item.description" class="info-row">
|
||||
<text class="label">备注:</text>
|
||||
<text class="value">{{ item.description }}</text>
|
||||
</view>
|
||||
<view v-if="item.checkReason" class="info-row check-reason">
|
||||
<text class="label">审核意见:</text>
|
||||
<text class="value">{{ item.checkReason }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-footer" v-if="canCancel(item.checkStatus) || canVisit(item.checkStatus)">
|
||||
<view class="btn-group">
|
||||
<view v-if="canVisit(item.checkStatus)" class="visit-btn" @tap.stop="handleVisit(item)">已到访</view>
|
||||
<view v-if="canCancel(item.checkStatus)" class="cancel-btn" @tap.stop="handleCancel(item)">取消</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view v-if="loading && !refreshing" class="loading">加载中...</view>
|
||||
<view v-if="!loading && !hasMore" class="no-more">没有更多数据了</view>
|
||||
<view v-if="!loading && visitorList.length === 0" class="empty">暂无访客记录</view>
|
||||
</scroll-view>
|
||||
|
||||
<tabbar :tabBarList="tabBarList" :current="tabbarStore.current" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from "vue"
|
||||
import visitorApi from "@/sheep/api/integrated/visitors"
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app"
|
||||
import useTabbarStore from "@/sheep/stores/tabbar"
|
||||
import tabbar from "@/sheep/components/tabbar.vue"
|
||||
|
||||
const tabbarStore = useTabbarStore()
|
||||
const tabBarList = computed(() => tabbarStore.tabbarList)
|
||||
|
||||
onLoad(() => {
|
||||
const app = getApp()
|
||||
app._onPageLoad()
|
||||
// 初始化 tabbar
|
||||
tabbarStore.initTabbar()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
// 设置当前页面索引
|
||||
const currentIndex = tabBarList.value.findIndex((item) => item.pagePath === "/pages/integrated/visitor/myvisitor")
|
||||
if (currentIndex !== -1) {
|
||||
tabbarStore.setCurrent(currentIndex)
|
||||
}
|
||||
})
|
||||
|
||||
// 标签页配置
|
||||
const tabs = ["全部", "待审核", "待到访", "已到访", "已取消"]
|
||||
const currentTab = ref(0)
|
||||
|
||||
// 列表数据
|
||||
const visitorList = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const refreshing = ref(false)
|
||||
const hasMore = ref(true)
|
||||
const current = ref(1)
|
||||
const size = 10
|
||||
|
||||
// 格式化访问时间
|
||||
const formatTime = (start: string, end: string) => {
|
||||
if (!start || !end) return ""
|
||||
const startDate = new Date(start)
|
||||
const endDate = new Date(end)
|
||||
return `${startDate.getMonth() + 1}/${startDate.getDate()} ${startDate.getHours()}:00-${endDate.getHours()}:00`
|
||||
}
|
||||
|
||||
// 格式化创建时间
|
||||
const formatCreateTime = (time: string) => {
|
||||
if (!time) return ""
|
||||
const date = new Date(time)
|
||||
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${date.getMinutes()}`
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status: string) => {
|
||||
const statusMap: Record<string, string> = {
|
||||
"1": "待审核",
|
||||
"2": "待到访",
|
||||
"0": "已到访",
|
||||
"5": "已取消",
|
||||
}
|
||||
return statusMap[status] || status
|
||||
}
|
||||
|
||||
// 获取状态样式
|
||||
const getStatusClass = (status: string) => {
|
||||
const statusMap: Record<string, string> = {
|
||||
"1": "status-pending",
|
||||
"2": "status-waiting",
|
||||
"0": "status-visited",
|
||||
"5": "status-cancelled",
|
||||
}
|
||||
return statusMap[status] || ""
|
||||
}
|
||||
|
||||
// 切换标签页
|
||||
const switchTab = (index: number) => {
|
||||
if (currentTab.value === index) return
|
||||
currentTab.value = index
|
||||
resetList()
|
||||
getVisitorList()
|
||||
}
|
||||
|
||||
// 重置列表状态
|
||||
const resetList = () => {
|
||||
current.value = 1
|
||||
visitorList.value = []
|
||||
hasMore.value = true
|
||||
}
|
||||
|
||||
// 获取访客记录列表
|
||||
const getVisitorList = async () => {
|
||||
if (loading.value || (!hasMore.value && !refreshing.value)) return
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
const params: any = {
|
||||
current: current.value,
|
||||
size,
|
||||
}
|
||||
|
||||
// 添加状态筛选
|
||||
if (currentTab.value > 0) {
|
||||
const statusMap = ["", "1", "2", "0", "5"]
|
||||
params.checkStatus = statusMap[currentTab.value]
|
||||
}
|
||||
|
||||
const { code, data }: any = await visitorApi.getVisitorList(params)
|
||||
|
||||
if (code === 0 && data) {
|
||||
if (refreshing.value || current.value === 1) {
|
||||
visitorList.value = data.records || []
|
||||
} else {
|
||||
visitorList.value.push(...(data.records || []))
|
||||
}
|
||||
|
||||
hasMore.value = (data.records || []).length === size
|
||||
if (hasMore.value) {
|
||||
current.value++
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取访客记录失败:", error)
|
||||
uni.showToast({
|
||||
title: "获取数据失败",
|
||||
icon: "none",
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
refreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const onRefresh = async () => {
|
||||
refreshing.value = true
|
||||
resetList()
|
||||
await getVisitorList()
|
||||
}
|
||||
|
||||
// 上拉加载更多
|
||||
const loadMore = () => {
|
||||
if (!loading.value && hasMore.value) {
|
||||
getVisitorList()
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否可以取消
|
||||
const canCancel = (status: string) => {
|
||||
return status === "1" || status === "2" || status === "5"
|
||||
}
|
||||
|
||||
// 判断是否可以标记到访
|
||||
const canVisit = (status: string) => {
|
||||
return status === "2"
|
||||
}
|
||||
|
||||
// 修改取消预约方法
|
||||
const handleCancel = (item: any) => {
|
||||
if (!item.id) return
|
||||
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "确定要取消此次预约吗?",
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: "取消中...",
|
||||
mask: true,
|
||||
})
|
||||
|
||||
const { code, msg }: any = await visitorApi.cancelVisitor(item.id)
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
if (code === 0) {
|
||||
uni.showToast({
|
||||
title: "取消成功",
|
||||
icon: "success",
|
||||
})
|
||||
// 刷新列表
|
||||
resetList()
|
||||
getVisitorList()
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: msg || "取消失败",
|
||||
icon: "none",
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: "取消失败",
|
||||
icon: "none",
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 处理到访
|
||||
const handleVisit = (item: any) => {
|
||||
if (!item.id) return
|
||||
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "确认访客已到访?",
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: "处理中...",
|
||||
mask: true,
|
||||
})
|
||||
|
||||
const { code, msg }: any = await visitorApi.haveVisitor(item.id)
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
if (code === 0) {
|
||||
uni.showToast({
|
||||
title: "操作成功",
|
||||
icon: "success",
|
||||
})
|
||||
// 刷新列表
|
||||
resetList()
|
||||
getVisitorList()
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: msg || "操作失败",
|
||||
icon: "none",
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: "操作失败",
|
||||
icon: "none",
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到详情页
|
||||
const goToDetail = (item: any) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/integrated/visitor/visitorDetail?id=${item.id}&token=${item.token}`,
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getVisitorList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.visitor-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
padding: 20rpx 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 20rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
color: #1777ff;
|
||||
font-weight: bold;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 40rpx;
|
||||
height: 4rpx;
|
||||
background-color: #1777ff;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-content {
|
||||
flex: 1;
|
||||
height: calc(100vh - 100rpx);
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.visitor-item {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.visit-time {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
|
||||
&.status-pending {
|
||||
background-color: #fff7e6;
|
||||
color: #fa8c16;
|
||||
}
|
||||
|
||||
&.status-waiting {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
&.status-visited {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
&.status-cancelled {
|
||||
background-color: #fff1f0;
|
||||
color: #f5222d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-content {
|
||||
.info-row {
|
||||
display: flex;
|
||||
margin-bottom: 12rpx;
|
||||
font-size: 26rpx;
|
||||
|
||||
.label {
|
||||
color: #999;
|
||||
width: 140rpx;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&.check-reason {
|
||||
.value {
|
||||
color: #f5222d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-footer {
|
||||
margin-top: 20rpx;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid #eee;
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 20rpx;
|
||||
|
||||
.visit-btn,
|
||||
.cancel-btn {
|
||||
padding: 10rpx 30rpx;
|
||||
border-radius: 30rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.visit-btn {
|
||||
background-color: #1777ff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #fff;
|
||||
color: #666;
|
||||
border: 1rpx solid #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading,
|
||||
.no-more,
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 30rpx;
|
||||
color: #999;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -10,4 +10,31 @@ export default {
|
||||
method: "GET",
|
||||
params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取会议室时间段
|
||||
*/
|
||||
getTimeSlots: (roomId: string, date: string) =>
|
||||
request({
|
||||
url: `/integrated/meetingRoom/${roomId}/time-slots`,
|
||||
method: "GET",
|
||||
params: { date },
|
||||
}),
|
||||
|
||||
/**
|
||||
* 预约会议室
|
||||
*/
|
||||
bookMeetingRoom: (roomId: string, data: {
|
||||
date: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
title: string;
|
||||
description: string;
|
||||
attendees: string[];
|
||||
}) =>
|
||||
request({
|
||||
url: `/integrated/meetingRoom/${roomId}/book`,
|
||||
method: "POST",
|
||||
data,
|
||||
}),
|
||||
}
|
||||
Reference in new issue
Block a user