移动端支持工作待办审批,修改会议预约流程,支持会务,桌子摆放要求选择

This commit is contained in:
zjf committed 2025-09-11 17:47:29 +08:00
1 parent 41d239e574
commit ca7d473ea3
5 files changed
+2051 -176

No files matched your search

+344 -18
View File
@@ -53,7 +53,57 @@
</view>
<!-- 预约按钮 -->
<view class="reserve-btn" @tap="reserveMeeting">发起会议</view>
<view class="reserve-btn" @tap="showReserveModal">发起会议</view>
<!-- 预约弹窗 -->
<view v-if="showModal" class="modal-overlay" @tap="hideModal">
<view class="modal-content" @tap.stop>
<view class="modal-header">
<text class="modal-title">会议预约</text>
<text class="modal-close" @tap="hideModal">×</text>
</view>
<view class="modal-body">
<!-- 会务服务选择 -->
<view class="form-item">
<text class="form-label">是否需要会务服务</text>
<view class="service-options">
<view
v-for="option in serviceOptions"
:key="option.value"
:class="['service-option', needService === option.value ? 'active' : '']"
@tap="selectService(option.value)"
>
<text class="option-text">{{ option.label }}</text>
</view>
</view>
</view>
<!-- 桌子摆放形态选择 -->
<view class="form-item">
<text class="form-label">桌子摆放形态</text>
<view class="layout-options">
<view
v-for="layout in deskLayouts"
:key="layout.id"
:class="['layout-option', selectedLayout === layout.id ? 'active' : '']"
@tap="selectLayout(layout.id)"
>
<text class="layout-name">{{ layout.name }}</text>
<text class="layout-desc">{{ layout.description }}</text>
</view>
</view>
</view>
</view>
<view class="modal-footer">
<view class="modal-btn cancel-btn" @tap="hideModal" :class="{ disabled: isBooking }">取消</view>
<view class="modal-btn confirm-btn" @tap="confirmReserve" :class="{ disabled: isBooking }">
{{ isBooking ? '预约中...' : '确认预约' }}
</view>
</view>
</view>
</view>
</view>
</template>
@@ -120,6 +170,19 @@ const selectedTimeText = computed(() => {
})
const selectedDuration = computed(() => selectedTime.value.length * 30)
// 弹窗相关数据
const showModal = ref(false)
const needService = ref('0') // 默认不需要会务服务
const selectedLayout = ref('') // 选中的桌子摆放形态ID
const deskLayouts = ref<any[]>([]) // 桌子摆放形态列表
const isBooking = ref(false) // 预约中状态
// 会务服务选项
const serviceOptions = [
{ value: '0', label: '不需要' },
{ value: '1', label: '需要' }
]
// 获取日期字符串
const getDateString = (index: number) => {
const date = new Date()
@@ -172,6 +235,24 @@ const fetchTimeSlots = async (date: string) => {
}
}
// 获取桌子摆放形态数据
const fetchDeskLayouts = async () => {
try {
const res = await meetingRoomApi.getMeetingDeskLayout('')
console.log('Desk layouts response:', res)
const response = res as unknown as ApiResponse<any[]>
if (response.code === 0 && response.data) {
deskLayouts.value = response.data
console.log('Desk layouts data:', deskLayouts.value)
} else {
console.error('Failed to fetch desk layouts:', response)
}
} catch (error) {
console.error('API request failed:', error)
}
}
// 选择时间
function selectTime(index: number) {
const slot = timeSlots.value[index]
@@ -200,20 +281,52 @@ function selectTime(index: number) {
}
}
// 预约会议
async function reserveMeeting() {
// 显示预约弹窗
function showReserveModal() {
if (selectedTime.value.length === 0) {
uni.showToast({ title: '请选择时间', icon: 'none' })
return
}
showModal.value = true
}
// 隐藏弹窗
function hideModal() {
showModal.value = false
}
// 选择会务服务
function selectService(value: string) {
needService.value = value
}
// 选择桌子摆放形态
function selectLayout(layoutId: string) {
selectedLayout.value = layoutId
}
// 确认预约
async function confirmReserve() {
if (!roomId.value) {
uni.showToast({ title: '会议室ID不存在', icon: 'none' })
return
}
// 防止重复点击
if (isBooking.value) {
return
}
// 设置预约中状态
isBooking.value = true
// 显示loading
uni.showLoading({
title: '预约中...',
mask: true
})
try {
uni.showLoading({ title: '预约中...', mask: true })
const date = getDateString(activeDate.value)
const startSlot = timeSlots.value[selectedTime.value[0]]
const endSlot = timeSlots.value[selectedTime.value[selectedTime.value.length - 1]]
@@ -224,29 +337,83 @@ async function reserveMeeting() {
endTime: endSlot.endTime,
title: room.value.name || '会议',
description: '',
attendees: []
attendees: [],
need_service: needService.value,
desk_layout: selectedLayout.value
})
const response = res as unknown as ApiResponse<any>
if (response.code === 0) {
uni.hideLoading()
uni.showToast({ title: '预约成功', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
console.log('预约接口响应:', res)
console.log('响应类型:', typeof res)
console.log('响应内容:', JSON.stringify(res))
// 处理响应数据
const responseData = res as any
// 检查业务错误(HTTP 200 但 code !== 0)
if (responseData && responseData.code !== undefined) {
console.log('检测到响应码:', responseData.code)
console.log('检测到响应消息:', responseData.msg)
if (responseData.code === 0) {
// 成功:隐藏loading,显示成功提示
uni.hideLoading()
isBooking.value = false
uni.showToast({ title: '预约成功', icon: 'success' })
hideModal()
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
// 失败:隐藏loading,关闭弹窗,显示错误信息
uni.hideLoading()
isBooking.value = false
console.log('预约失败,错误信息:', responseData.msg)
// 先关闭弹窗,再显示错误信息
hideModal()
// 延迟显示错误信息,确保弹窗完全关闭
setTimeout(() => {
uni.showToast({
title: responseData.msg || '预约失败',
icon: 'none',
duration: 3000
})
}, 300)
}
} else {
// 响应格式异常:隐藏loading,显示错误信息
uni.hideLoading()
isBooking.value = false
console.log('响应格式异常:', responseData)
uni.showToast({
title: response.msg || '预约失败',
icon: 'none'
title: '预约失败,响应格式异常',
icon: 'none',
duration: 3000
})
}
} catch (error) {
// 确保在异常情况下也隐藏loading
uni.hideLoading()
uni.showToast({
title: '预约失败',
icon: 'none'
})
isBooking.value = false
console.error('预约失败:', error)
// 处理HTTP错误(如500错误)
const errorData = error as any
if (errorData?.data?.msg) {
console.log('HTTP错误信息:', errorData.data.msg)
uni.showToast({
title: errorData.data.msg,
icon: 'none',
duration: 3000
})
} else {
uni.showToast({
title: '预约失败,请重试',
icon: 'none',
duration: 3000
})
}
}
}
@@ -284,6 +451,9 @@ onMounted(() => {
console.log('Fetching time slots for date:', today)
fetchTimeSlots(today)
// 获取桌子摆放形态数据
fetchDeskLayouts()
// 从 localStorage 获取用户信息
try {
const userInfoString = localStorage.getItem('user_info')
@@ -489,4 +659,160 @@ const goBack = () => {
padding: 24rpx 0;
font-weight: 600;
}
/* 弹窗样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
width: 90%;
max-width: 600rpx;
background: #fff;
border-radius: 16rpx;
overflow: hidden;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-bottom: 1px solid #f0f0f0;
}
.modal-title {
font-size: 32rpx;
font-weight: 600;
color: #222;
}
.modal-close {
font-size: 40rpx;
color: #999;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.modal-body {
padding: 32rpx;
}
.form-item {
margin-bottom: 32rpx;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
font-weight: 500;
}
.service-options {
display: flex;
gap: 16rpx;
}
.service-option {
flex: 1;
padding: 20rpx;
border: 2rpx solid #e0e0e0;
border-radius: 8rpx;
text-align: center;
background: #fff;
transition: all 0.3s;
}
.service-option.active {
border-color: #1777FF;
background: #e6f0ff;
color: #1777FF;
}
.option-text {
font-size: 26rpx;
}
.layout-options {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.layout-option {
padding: 20rpx;
border: 2rpx solid #e0e0e0;
border-radius: 8rpx;
background: #fff;
transition: all 0.3s;
}
.layout-option.active {
border-color: #1777FF;
background: #e6f0ff;
}
.layout-name {
display: block;
font-size: 26rpx;
color: #333;
font-weight: 500;
margin-bottom: 4rpx;
}
.layout-option.active .layout-name {
color: #1777FF;
}
.layout-desc {
display: block;
font-size: 22rpx;
color: #666;
}
.layout-option.active .layout-desc {
color: #1777FF;
}
.modal-footer {
display: flex;
border-top: 1px solid #f0f0f0;
}
.modal-btn {
flex: 1;
padding: 24rpx;
text-align: center;
font-size: 28rpx;
font-weight: 500;
}
.cancel-btn {
color: #666;
border-right: 1px solid #f0f0f0;
}
.confirm-btn {
color: #1777FF;
font-weight: 600;
}
.modal-btn.disabled {
opacity: 0.5;
pointer-events: none;
}
</style>
File diff suppressed because it is too large. Load diff
+63 -2
View File
@@ -7,7 +7,10 @@
<div class="service-container">
<div class="service-grid">
<div v-for="item in services" :key="item.name" class="service-item" @click="goPage(item)">
<image :src="item.icon" class="service-icon" />
<div class="service-icon-wrapper">
<image :src="item.icon" class="service-icon" />
<view v-if="item.name === 'todo' && todoCount > 0" class="badge">{{ todoCount > 99 ? '99+' : todoCount }}</view>
</div>
<div class="service-label">{{ item.label }}</div>
</div>
</div>
@@ -18,6 +21,10 @@
<script setup lang="ts">
import { onLoad } from "@dcloudio/uni-app"
import { ref, onMounted } from "vue"
import integratedApi from "@/sheep/api/integrated"
const todoCount = ref(0)
const services = [
{
@@ -64,11 +71,29 @@ const services = [
},
]
// 获取待办数量
async function fetchTodoCount() {
try {
const res = await integratedApi.getTodoList({ current: 1, size: 1 })
const responseData = res as any
if (responseData.code === 0 && responseData.data) {
todoCount.value = responseData.data.total || 0
}
} catch (error) {
console.error('获取待办数量失败:', error)
todoCount.value = 0
}
}
onLoad(() => {
const app = getApp()
app._initTabbar()
})
onMounted(() => {
fetchTodoCount()
})
function goPage(item) {
if (item.url) {
uni.navigateTo({ url: item.url })
@@ -122,15 +147,51 @@ function goPage(item) {
transform: translateY(-6rpx) scale(1.06);
box-shadow: 0 2rpx 12rpx rgba(25, 118, 210, 0.08);
}
.service-icon-wrapper {
position: relative;
margin-bottom: 14rpx;
}
.service-icon {
width: 92rpx;
height: 92rpx;
margin-bottom: 14rpx;
border-radius: 16rpx;
background: #f5f7fa;
box-shadow: 0 2rpx 8rpx rgba(25, 118, 210, 0.04);
transition: box-shadow 0.2s;
}
.badge {
position: absolute;
top: -8rpx;
right: -8rpx;
background: #ff6b35;
color: #fff;
font-size: 20rpx;
font-weight: bold;
min-width: 32rpx;
height: 32rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0 8rpx;
box-shadow: 0 2rpx 8rpx rgba(255, 107, 53, 0.4);
border: 2rpx solid #fff;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
.service-label {
font-size: 28rpx;
color: #333;
+11 -147
View File
@@ -31,6 +31,8 @@ export default {
title: string;
description: string;
attendees: string[];
need_service?: string;
desk_layout?: string;
}) =>
request({
url: `/integrated/meetingRoom/${roomId}/book`,
@@ -57,154 +59,16 @@ export default {
method: 'GET',
}),
// ========== 意见反馈相关接口 ==========
/**
* 提交意见反馈
*/
submitFeedback: (data: {
title: string;
description: string;
picUrl?: string;
type: string;
contact?: string;
}) =>
request({
url: '/integrated/feedback/app/save',
method: 'POST',
data,
}),
/**
* 获取我的反馈列表
*/
getFeedbackList: (params: { current: number; size: number }) =>
request({
url: '/integrated/feedback/app/page',
method: 'GET',
params,
}),
/**
* 获取桌子拜访形态
*/
getMeetingDeskLayout: (id: string) =>
request({
url: `/integrated/meetingDeskLayout/app/list`,
method: 'GET',
}),
/**
* 获取反馈详情
*/
getFeedbackDetail: (id: string) =>
request({
url: '/integrated/feedback/app/getDetails',
method: 'GET',
params: { id },
}),
}
/**
* 获取反馈类型字典
*/
getFeedbackType: () =>
request({
url: '/integrated/feedback/app/getFeedbackType',
method: 'GET',
}),
// ========== 维修上报相关接口 ==========
/**
* 提交维修申请
*/
submitRepair: (data: {
title: string;
picUrl?: string;
description: string;
dateFound: string;
location: string;
urgency: string;
maintainType: number;
}) =>
request({
url: '/integrated/maintenanceOrder/app/apply',
method: 'POST',
data,
}),
/**
* 获取我的维修记录列表
*/
getRepairList: (params: { current: number; size: number }) =>
request({
url: '/integrated/maintenanceOrder/app/my',
method: 'GET',
params,
}),
/**
* 获取维修详情
*/
getRepairDetail: (id: string | number) =>
request({
url: `/integrated/maintenanceOrder/app/my/${id}`,
method: 'GET',
}),
// ========== 通知公告相关接口 ==========
/**
* 获取公告列表
*/
getNoticeList: (params: { current: number; size: number; type?: string }) =>
request({
url: '/integrated/notice/list',
method: 'GET',
params,
}),
/**
* 获取公告详情
*/
getNoticeDetail: (id: string) =>
request({
url: `/integrated/notice/detail/${id}`,
method: 'GET',
}),
/**
* 标记公告已读
*/
markNoticeRead: (id: string) =>
request({
url: `/integrated/notice/read/${id}`,
method: 'POST',
}),
// ========== 访客管理相关接口 ==========
/**
* 提交访客申请
*/
submitVisitor: (data: {
visitorName: string;
visitorPhone: string;
purpose: string;
visitDate: string;
visitTime: string;
hostName: string;
remark?: string;
}) =>
request({
url: '/integrated/visitor/submit',
method: 'POST',
data,
}),
/**
* 获取访客记录列表
*/
getVisitorList: (params: { current: number; size: number }) =>
request({
url: '/integrated/visitor/my-list',
method: 'GET',
params,
}),
/**
* 获取访客详情
*/
getVisitorDetail: (id: string) =>
request({
url: `/integrated/visitor/detail/${id}`,
method: 'GET',
}),
}
+29
View File
@@ -20,4 +20,33 @@ export default {
url: `/integrated/notice/app/view/${id}`,
method: 'POST',
}),
// ========== 通知公告相关接口 ==========
/**
* 获取公告列表
*/
getNoticeList: (params: { current: number; size: number; type?: string }) =>
request({
url: '/integrated/notice/list',
method: 'GET',
params,
}),
/**
* 获取公告详情
*/
getNoticeDetailById: (id: string) =>
request({
url: `/integrated/notice/detail/${id}`,
method: 'GET',
}),
/**
* 标记公告已读
*/
markNoticeRead: (id: string) =>
request({
url: `/integrated/notice/read/${id}`,
method: 'POST',
}),
}