Files
qipai/miniapp/pages/manager/dashboard.js
T

306 lines
11 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: '已关闭',
}
const orderActionsByStatus = {
DRAFT: [{ action: 'SUBMIT', label: '提交订单' }, { action: 'CANCEL', label: '取消订单' }, { action: 'CLOSE', label: '关闭订单' }],
PENDING_PAYMENT: [{ action: 'CANCEL', label: '取消订单' }, { action: 'CLOSE', label: '关闭订单' }],
PAID: [{ action: 'RESERVE', label: '确认预留' }, { action: 'START', label: '开始使用' }, { action: 'CANCEL', label: '取消订单' }, { action: 'BEGIN_REFUND', label: '发起退款' }],
RESERVED: [{ action: 'START', label: '开始使用' }, { action: 'CANCEL', label: '取消订单' }, { action: 'BEGIN_REFUND', label: '发起退款' }],
IN_PROGRESS: [{ action: 'FINISH', label: '结束使用' }, { action: 'BEGIN_REFUND', label: '发起退款' }],
FINISHED: [{ action: 'BEGIN_REFUND', label: '发起退款' }, { action: 'CLOSE', label: '关闭订单' }],
CANCELLED: [{ action: 'BEGIN_REFUND', label: '发起退款' }, { action: 'CLOSE', label: '关闭订单' }],
REFUNDING: [{ action: 'COMPLETE_REFUND', label: '确认退款完成' }],
REFUNDED: [{ action: 'CLOSE', label: '关闭订单' }],
}
Page({
data: {
loading: false,
errorMessage: '',
canWrite: false,
stores: [],
selectedStoreId: '',
selectedStoreName: '',
rooms: [],
orders: [],
visibleOrders: [],
orderFilters: [
{ value: '', label: '全部' },
{ value: 'PENDING_PAYMENT', label: '待支付' },
{ value: 'RESERVED', label: '待开始' },
{ value: 'IN_PROGRESS', label: '进行中' },
{ value: 'FINISHED', label: '已结束' },
{ value: 'REFUNDING', label: '退款中' },
],
selectedOrderStatus: '',
busyRoomId: '',
busyOrderId: '',
summary: {
roomTotal: 0,
available: 0,
inUse: 0,
attention: 0,
activeOrders: 0,
},
},
async onLoad() {
await this.loadDashboard()
},
async onShow() {
if (this.data.selectedStoreId) await this.loadStoreData(this.data.selectedStoreId)
},
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 = await request('/management/stores')
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,
selectedStoreId,
selectedStoreName: stores.find((store) => store.id === selectedStoreId)?.name || '',
})
await this.loadStoreData(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 || '',
selectedOrderStatus: '',
})
await this.loadStoreData(storeId)
},
async loadStoreData(storeId) {
if (!storeId) {
this.setData({ rooms: [], orders: [], visibleOrders: [] })
this.refreshSummary()
return
}
try {
const encodedStoreId = encodeURIComponent(storeId)
const [roomsResponse, ordersResponse] = await Promise.all([
request(`/management/stores/${encodedStoreId}/rooms`),
request(`/orders?page=1&pageSize=50&storeId=${encodedStoreId}`),
])
const rooms = (roomsResponse.data || []).map((item) => ({
...item,
statusText: roomStatusLabels[item.operationalStatus] || item.operationalStatus,
priceText: cents(item.basePriceCents),
}))
const orders = (ordersResponse.data?.items || []).map((item) => this.presentOrder(item))
this.setData({ rooms, orders })
this.applyOrderFilter()
this.refreshSummary()
} catch (error) {
this.setData({ errorMessage: error.message || '门店运营数据加载失败' })
}
},
selectOrderFilter(event) {
this.setData({ selectedOrderStatus: event.currentTarget.dataset.status || '' })
this.applyOrderFilter()
},
applyOrderFilter() {
const status = this.data.selectedOrderStatus
this.setData({
visibleOrders: status
? this.data.orders.filter((item) => item.status === status)
: this.data.orders,
})
},
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.orders.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.loadStoreData(this.data.selectedStoreId)
wx.showToast({ title: '房态已更新', icon: 'success' })
} catch (error) {
this.setData({ errorMessage: error.message || '房态更新失败' })
} finally {
this.setData({ busyRoomId: '' })
}
},
createOrder() {
if (!this.data.canWrite || !this.data.selectedStoreId) return
wx.navigateTo({
url: `/pages/manager/order-create?storeId=${encodeURIComponent(this.data.selectedStoreId)}&storeName=${encodeURIComponent(this.data.selectedStoreName)}`,
})
},
manageOrder(event) {
if (!this.data.canWrite) return
const orderId = event.currentTarget.dataset.orderId
const status = event.currentTarget.dataset.status
const actions = orderActionsByStatus[status] || []
if (!actions.length) {
wx.showToast({ title: '当前状态无可用动作', icon: 'none' })
return
}
wx.showActionSheet({
itemList: actions.map((item) => item.label),
success: ({ tapIndex }) => {
const selected = actions[tapIndex]
wx.showModal({
title: selected.label,
content: `确认对订单执行“${selected.label}”吗?`,
success: ({ confirm }) => {
if (confirm) this.executeOrderAction(orderId, selected)
},
})
},
})
},
addOrderNote(event) {
if (!this.data.canWrite) return
const orderId = event.currentTarget.dataset.orderId
wx.showModal({
title: '添加订单备注',
editable: true,
placeholderText: '请输入备注内容',
success: ({ confirm, content }) => {
const note = String(content || '').trim()
if (confirm && note) this.saveOrderNote(orderId, note)
},
})
},
async executeOrderAction(orderId, selected) {
if (!orderId || this.data.busyOrderId) return
this.setData({ busyOrderId: orderId, errorMessage: '' })
try {
await request(`/management/orders/${encodeURIComponent(orderId)}/actions`, {
method: 'POST',
data: { action: selected.action, reason: `管理员小程序:${selected.label}` },
})
await this.loadStoreData(this.data.selectedStoreId)
wx.showToast({ title: '订单已更新', icon: 'success' })
} catch (error) {
this.setData({ errorMessage: error.message || '订单操作失败' })
} finally {
this.setData({ busyOrderId: '' })
}
},
async saveOrderNote(orderId, note) {
if (!orderId || this.data.busyOrderId) return
this.setData({ busyOrderId: orderId, errorMessage: '' })
try {
await request(`/management/orders/${encodeURIComponent(orderId)}/note`, {
method: 'POST',
data: { note },
})
wx.showToast({ title: '备注已保存', icon: 'success' })
} catch (error) {
this.setData({ errorMessage: error.message || '订单备注保存失败' })
} finally {
this.setData({ busyOrderId: '' })
}
},
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())}`
},
})