192 lines
6.5 KiB
JavaScript
192 lines
6.5 KiB
JavaScript
const { request, ensureLogin, cents } = require('../../utils/api.js')
|
|
|
|
const managerRoles = ['STAFF', 'STORE_ADMIN', 'TENANT_ADMIN', 'PLATFORM_ADMIN']
|
|
const roomStatusLabels = {
|
|
AVAILABLE: '空闲',
|
|
MAINTENANCE: '维护中',
|
|
RESERVED: '已预订',
|
|
IN_USE: '使用中',
|
|
CLEANING_REQUIRED: '待清洁',
|
|
}
|
|
const orderStatusLabels = {
|
|
DRAFT: '草稿',
|
|
PENDING_PAYMENT: '待支付',
|
|
PAID: '已支付',
|
|
RESERVED: '待开始',
|
|
IN_PROGRESS: '进行中',
|
|
FINISHED: '已结束',
|
|
CANCELLED: '已取消',
|
|
REFUNDING: '退款中',
|
|
REFUNDED: '已退款',
|
|
CLOSED: '已关闭',
|
|
}
|
|
|
|
Page({
|
|
data: {
|
|
loading: false,
|
|
errorMessage: '',
|
|
canWrite: false,
|
|
stores: [],
|
|
selectedStoreId: '',
|
|
selectedStoreName: '',
|
|
rooms: [],
|
|
orders: [],
|
|
visibleOrders: [],
|
|
busyRoomId: '',
|
|
summary: {
|
|
roomTotal: 0,
|
|
available: 0,
|
|
inUse: 0,
|
|
attention: 0,
|
|
activeOrders: 0,
|
|
},
|
|
},
|
|
|
|
async onLoad() {
|
|
await this.loadDashboard()
|
|
},
|
|
|
|
async onPullDownRefresh() {
|
|
await this.loadDashboard(this.data.selectedStoreId)
|
|
wx.stopPullDownRefresh()
|
|
},
|
|
|
|
async loadDashboard(preferredStoreId = '') {
|
|
this.setData({ loading: true, errorMessage: '' })
|
|
try {
|
|
await ensureLogin()
|
|
const me = await request('/auth/me')
|
|
const access = me.data?.access || { roles: [], capabilities: [], storeIds: [] }
|
|
if (!access.roles.some((role) => managerRoles.includes(role))) {
|
|
throw new Error('当前账号没有门店运营权限')
|
|
}
|
|
const [storesResponse, ordersResponse] = await Promise.all([
|
|
request('/management/stores'),
|
|
request('/orders?page=1&pageSize=50'),
|
|
])
|
|
const stores = storesResponse.data || []
|
|
const selectedStoreId = stores.some((store) => store.id === preferredStoreId)
|
|
? preferredStoreId
|
|
: (stores[0]?.id || '')
|
|
this.setData({
|
|
canWrite: access.capabilities.includes('store.operation.write')
|
|
|| access.capabilities.includes('tenant.manage')
|
|
|| access.roles.includes('PLATFORM_ADMIN'),
|
|
stores,
|
|
orders: (ordersResponse.data?.items || []).map((item) => this.presentOrder(item)),
|
|
selectedStoreId,
|
|
selectedStoreName: stores.find((store) => store.id === selectedStoreId)?.name || '',
|
|
})
|
|
await this.loadRooms(selectedStoreId)
|
|
} catch (error) {
|
|
this.setData({ errorMessage: error.message || '门店运营数据加载失败' })
|
|
} finally {
|
|
this.setData({ loading: false })
|
|
}
|
|
},
|
|
|
|
async selectStore(event) {
|
|
const storeId = event.currentTarget.dataset.storeId
|
|
if (!storeId || storeId === this.data.selectedStoreId) return
|
|
const store = this.data.stores.find((item) => item.id === storeId)
|
|
this.setData({ selectedStoreId: storeId, selectedStoreName: store?.name || '' })
|
|
await this.loadRooms(storeId)
|
|
},
|
|
|
|
async loadRooms(storeId) {
|
|
if (!storeId) {
|
|
this.setData({ rooms: [], visibleOrders: [] })
|
|
this.refreshSummary()
|
|
return
|
|
}
|
|
try {
|
|
const response = await request(`/management/stores/${encodeURIComponent(storeId)}/rooms`)
|
|
const rooms = (response.data || []).map((item) => ({
|
|
...item,
|
|
statusText: roomStatusLabels[item.operationalStatus] || item.operationalStatus,
|
|
priceText: cents(item.basePriceCents),
|
|
}))
|
|
this.setData({
|
|
rooms,
|
|
visibleOrders: this.data.orders.filter((item) => item.storeId === storeId),
|
|
})
|
|
this.refreshSummary()
|
|
} catch (error) {
|
|
this.setData({ errorMessage: error.message || '房态加载失败' })
|
|
}
|
|
},
|
|
|
|
refreshSummary() {
|
|
const rooms = this.data.rooms
|
|
const activeStatuses = ['PAID', 'RESERVED', 'IN_PROGRESS']
|
|
this.setData({
|
|
summary: {
|
|
roomTotal: rooms.length,
|
|
available: rooms.filter((item) => item.configurationStatus === 'ENABLED' && item.operationalStatus === 'AVAILABLE').length,
|
|
inUse: rooms.filter((item) => ['RESERVED', 'IN_USE'].includes(item.operationalStatus)).length,
|
|
attention: rooms.filter((item) => item.configurationStatus === 'DISABLED' || ['MAINTENANCE', 'CLEANING_REQUIRED'].includes(item.operationalStatus)).length,
|
|
activeOrders: this.data.visibleOrders.filter((item) => activeStatuses.includes(item.status)).length,
|
|
},
|
|
})
|
|
},
|
|
|
|
changeRoomStatus(event) {
|
|
if (!this.data.canWrite) return
|
|
const roomId = event.currentTarget.dataset.roomId
|
|
wx.showActionSheet({
|
|
itemList: ['设为空闲', '设为维护中', '设为待清洁'],
|
|
success: ({ tapIndex }) => {
|
|
const statuses = ['AVAILABLE', 'MAINTENANCE', 'CLEANING_REQUIRED']
|
|
this.updateRoomStatus(roomId, { operationalStatus: statuses[tapIndex] }, '管理员小程序调整房态')
|
|
},
|
|
})
|
|
},
|
|
|
|
toggleRoomConfiguration(event) {
|
|
if (!this.data.canWrite) return
|
|
const roomId = event.currentTarget.dataset.roomId
|
|
const current = event.currentTarget.dataset.status
|
|
const configurationStatus = current === 'ENABLED' ? 'DISABLED' : 'ENABLED'
|
|
this.updateRoomStatus(roomId, { configurationStatus }, configurationStatus === 'ENABLED' ? '管理员小程序启用房间' : '管理员小程序停用房间')
|
|
},
|
|
|
|
async updateRoomStatus(roomId, status, reason) {
|
|
if (!roomId || this.data.busyRoomId) return
|
|
this.setData({ busyRoomId: roomId, errorMessage: '' })
|
|
try {
|
|
await request(`/management/rooms/${encodeURIComponent(roomId)}/status`, {
|
|
method: 'PATCH',
|
|
data: { storeId: this.data.selectedStoreId, ...status, reason },
|
|
})
|
|
await this.loadRooms(this.data.selectedStoreId)
|
|
wx.showToast({ title: '房态已更新', icon: 'success' })
|
|
} catch (error) {
|
|
this.setData({ errorMessage: error.message || '房态更新失败' })
|
|
} finally {
|
|
this.setData({ busyRoomId: '' })
|
|
}
|
|
},
|
|
|
|
openOrder(event) {
|
|
const orderId = event.currentTarget.dataset.orderId
|
|
if (!orderId) return
|
|
wx.navigateTo({ url: `/pages/orders/detail?orderId=${encodeURIComponent(orderId)}` })
|
|
},
|
|
|
|
presentOrder(item) {
|
|
return {
|
|
...item,
|
|
statusText: orderStatusLabels[item.status] || item.status,
|
|
amountText: cents(item.paidAmountCents || item.totalAmountCents),
|
|
timeText: `${this.formatTime(item.startAt)} - ${this.formatTime(item.endAt)}`,
|
|
}
|
|
},
|
|
|
|
formatTime(value) {
|
|
const date = new Date(value)
|
|
if (Number.isNaN(date.getTime())) return String(value || '')
|
|
const pad = (part) => String(part).padStart(2, '0')
|
|
return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
|
|
},
|
|
})
|