员工端h5版本代码提交

This commit is contained in:
andy
2026-06-16 15:12:54 +08:00
commit 43a617e5da
45 changed files with 4754 additions and 0 deletions

56
src/api/auth.ts Normal file
View File

@@ -0,0 +1,56 @@
import http from './request'
import { mockApi } from './mock'
import { env } from '@/utils/env'
import type { TokenResponse } from '@/types/api'
import { joinUrl } from '@/utils/url'
export interface OrganizationMemberInfo {
memberId?: string
organizationId?: string
memberName?: string
userPhone?: string
memberRoleId?: string
}
const basicAuth = () => {
const raw = `${env.clientId}:${env.clientSecret}`
return `Basic ${window.btoa(raw)}`
}
export const sendMobileCode = async (phone: string) => {
if (env.useMock) return mockApi.sendMobileCode(phone)
return await http.get<boolean>(joinUrl(env.adminBase, 'platformUser/sendMobileCode', phone), {
headers: {
clientId: env.clientId,
clientConfigId: env.clientConfigId
}
}) as unknown as boolean
}
export const loginByMobile = async (phone: string, code: string): Promise<TokenResponse> => {
if (env.useMock) return mockApi.loginByMobile(phone)
const form = new URLSearchParams()
form.set('grant_type', 'mobile')
form.set('mobile', phone)
form.set('code', code)
form.set('scope', 'server')
if (env.clientConfigId) {
form.set('clientConfigId', env.clientConfigId)
}
return await http.post<TokenResponse>(joinUrl(env.authBase, 'oauth2/token'), form, {
headers: {
Authorization: basicAuth(),
'Content-Type': 'application/x-www-form-urlencoded'
}
}) as unknown as TokenResponse
}
export const bindUserInfoAndGetUserMemberInfoByPhone = async (phone: string) => {
if (env.useMock) return mockApi.bindOrganization(phone)
return await http.post<OrganizationMemberInfo>(
joinUrl(env.staffBase, 'organizationMember/bindUserInfoAndGetUserMemberInfoByPhone'),
{ userPhone: phone }
) as unknown as OrganizationMemberInfo
}

19
src/api/events.ts Normal file
View File

@@ -0,0 +1,19 @@
import http from './request'
import { mockApi } from './mock'
import { env } from '@/utils/env'
import type { PageResponse } from '@/types/api'
import type { CreateEventPayload, EventData, EventSearch } from '@/types/event'
import { joinUrl } from '@/utils/url'
export const fetchEventList = async (query: EventSearch): Promise<PageResponse<EventData>> => {
if (env.useMock) return mockApi.eventList(query)
return await http.post<PageResponse<EventData>>(
joinUrl(env.staffBase, 'event/eventList'),
query
) as unknown as PageResponse<EventData>
}
export const createEvent = async (payload: CreateEventPayload) => {
if (env.useMock) return mockApi.createEvent(payload)
return http.post<boolean>(joinUrl(env.staffBase, 'event/createEvent'), payload)
}

295
src/api/mock.ts Normal file
View File

@@ -0,0 +1,295 @@
import dayjs from 'dayjs'
import type { PageResponse, TokenResponse } from '@/types/api'
import type {
CommodityOrder,
CommodityOrderSearch,
UserOrderDetail,
WriteOffPayload,
WriteOffResult
} from '@/types/order'
import type { CreateEventPayload, EventData, EventSearch } from '@/types/event'
import { addDays, formatDateTime, today } from '@/utils/date'
const wait = (ms = 260) => new Promise((resolve) => window.setTimeout(resolve, ms))
const coverImages = [
'https://images.unsplash.com/photo-1566073771259-6a8506099945?auto=format&fit=crop&w=900&q=80',
'https://images.unsplash.com/photo-1542314831-068cd1dbfeeb?auto=format&fit=crop&w=900&q=80',
'https://images.unsplash.com/photo-1551882547-ff40c63fe5fa?auto=format&fit=crop&w=900&q=80'
]
let orders: CommodityOrder[] = [
{
id: '202606160001',
orderAmt: '699.00',
visitorName: '林清',
contactPhone: '13800000001',
checkInData: today(),
checkOutData: addDays(1),
orderStatus: '2',
payStatus: '1',
payAmt: '659.00',
discountAmt: '40.00',
payTime: formatDateTime(dayjs().subtract(2, 'hour').toDate()),
orderType: '0',
createTime: formatDateTime(dayjs().subtract(3, 'hour').toDate()),
roomId: '1208',
commodityName: '湖景大床房含双早',
commodityCoverPhoto: coverImages[0],
commodityAmount: 1,
reservationDate: today()
},
{
id: '202606150108',
orderAmt: '238.00',
visitorName: '王予安',
contactPhone: '13800000002',
orderStatus: '6',
payStatus: '1',
payAmt: '238.00',
orderType: '2',
createTime: formatDateTime(dayjs().subtract(1, 'day').toDate()),
commodityName: '云端餐厅双人套餐',
commodityCoverPhoto: coverImages[1],
commodityAmount: 1,
writeOffTime: formatDateTime(dayjs().subtract(4, 'hour').toDate())
},
{
id: '202606140072',
orderAmt: '158.00',
visitorName: '赵一鸣',
contactPhone: '13800000003',
orderStatus: '4',
payStatus: '1',
payAmt: '158.00',
orderType: '1',
createTime: formatDateTime(dayjs().subtract(2, 'day').toDate()),
commodityName: '温泉中心成人票',
commodityCoverPhoto: coverImages[2],
commodityAmount: 2
},
{
id: '202606130045',
orderAmt: '899.00',
visitorName: '许知夏',
contactPhone: '13800000004',
checkInData: addDays(2),
checkOutData: addDays(3),
orderStatus: '1',
payStatus: '1',
payAmt: '899.00',
orderType: '0',
createTime: formatDateTime(dayjs().subtract(3, 'day').toDate()),
commodityName: '亲子套房一晚',
commodityCoverPhoto: coverImages[0],
commodityAmount: 1,
reservationDate: addDays(2)
}
]
let events: EventData[] = [
{
id: 'EVT20260616001',
entityName: '前台',
eventDescription: '端午假期入住高峰,前台增开一组接待人员并优先处理已预付订单。',
eventStatus: 0,
effectiveStartTime: formatDateTime(dayjs().subtract(1, 'hour').toDate()),
effectiveEndTime: formatDateTime(dayjs().add(8, 'hour').toDate()),
createByName: '值班经理',
popUpReminder: 1,
publicTime: formatDateTime(dayjs().subtract(2, 'hour').toDate()),
imgUrl: coverImages[0],
showStatusDesc: '生效中'
},
{
id: 'EVT20260615002',
entityName: '餐饮部',
eventDescription: '晚餐第二时段预约已满,建议推荐客人选择 19:45 后到店。',
eventStatus: 0,
effectiveStartTime: formatDateTime(dayjs().add(1, 'hour').toDate()),
effectiveEndTime: formatDateTime(dayjs().add(10, 'hour').toDate()),
createByName: '餐饮主管',
popUpReminder: 0,
publicTime: formatDateTime(dayjs().subtract(1, 'day').toDate()),
imgUrl: coverImages[1],
showStatusDesc: '待生效'
}
]
const page = <T>(records: T[], pageNum: number, pageSize: number): PageResponse<T> => {
const start = (pageNum - 1) * pageSize
const current = records.slice(start, start + pageSize)
return {
records: current,
total: records.length,
size: pageSize,
current: pageNum,
pages: Math.ceil(records.length / pageSize)
}
}
export const mockApi = {
async sendMobileCode(phone: string) {
await wait()
return { data: true, msg: phone ? '5678nianxx' : '请输入手机号' }
},
async loginByMobile(phone: string): Promise<TokenResponse> {
await wait()
return {
access_token: `mock-token-${Date.now()}`,
refresh_token: 'mock-refresh-token',
token_type: 'Bearer',
expires_in: 43200,
scope: 'server',
client_id: 'custom',
clientId: 'custom',
user_id: '10001',
username: phone,
tenant_id: 1
}
},
async checkBindPhone() {
await wait(120)
return true
},
async bindOrganization(phone: string) {
await wait(180)
if (!phone) {
throw new Error('未绑定组织,请联系管理员')
}
return {
memberId: 'MEMBER10001',
organizationId: 'ORG10001',
memberName: '当前员工',
userPhone: phone,
memberRoleId: 'ROLE_STAFF'
}
},
async orderList(query: CommodityOrderSearch) {
await wait()
const keyword = query.orderId || query.contactPhone || ''
const filtered = orders.filter((item) => {
const matchStatus = query.orderStatus ? item.orderStatus === query.orderStatus : true
const matchType = query.commodityTypeCode ? item.orderType === query.commodityTypeCode : true
const matchKeyword = keyword
? item.id.includes(keyword) || item.contactPhone.includes(keyword) || item.visitorName.includes(keyword)
: true
return matchStatus && matchType && matchKeyword
})
return page(filtered, query.pageNum, query.pageSize)
},
async orderDetail(orderId: string): Promise<UserOrderDetail> {
await wait()
const order = orders.find((item) => item.id === orderId) || orders[0]
return {
orderId: order.id,
orderAmt: order.orderAmt,
consumerInfoList: [
{
id: '1',
visitorName: order.visitorName,
contactPhone: order.contactPhone,
idCard: '430***********2218'
}
],
checkInData: order.checkInData,
checkOutData: order.checkOutData,
reservationDate: order.reservationDate,
orderStatus: order.orderStatus,
userId: order.userId,
organizationId: order.organizationId,
payStatus: order.payStatus,
payAmt: order.payAmt,
discountAmt: order.discountAmt,
payTime: order.payTime,
createTime: order.createTime,
commodityCoverPhoto: order.commodityCoverPhoto,
commodityName: order.commodityName,
commodityAddress: '星河湾酒店一层前台',
commodityServiceList: [{ serviceName: '到店核销' }, { serviceName: '不可拆分使用' }],
commodityTip: '请在预约日期到店使用,核销后不支持退款。',
payWay: '0',
paySerialNumber: `WX${order.id}`,
paySource: '2',
orderType: order.orderType,
commodityAmount: String(order.commodityAmount),
storeName: '星河湾酒店',
complaintHotline: '18285123187',
refundable: order.orderStatus !== '6',
commodityId: `COM${order.id.slice(-4)}`,
commodityTypeCode: order.orderType,
commodityFacilityList: ['含早餐', '免费停车', '可预约'],
commodityDescription: '面向到店客人的酒店权益商品,需由员工确认后核销。',
commodityPackageConfig: [
{
packageName: order.commodityName,
packageContent: `${order.commodityName} x ${order.commodityAmount}`
}
],
reservationEnabled: order.reservationDate ? 1 : 0,
reservationRuleDesc: '预约订单请核对预约日期和联系人信息。',
writeOffRecordList: order.writeOffTime
? [
{
id: `WO${order.id}`,
orderId: order.id,
packageName: order.commodityName,
userName: '当前员工',
createTime: order.writeOffTime
}
]
: []
}
},
async writeOff(payload: WriteOffPayload): Promise<WriteOffResult> {
await wait()
orders = orders.map((item) =>
item.id === payload.orderId
? { ...item, orderStatus: '6', writeOffTime: formatDateTime(new Date()) }
: item
)
return {
success: true,
orderId: payload.orderId,
packageName: payload.packageName,
message: '核销成功',
operatedAt: formatDateTime(new Date())
}
},
async eventList(query: EventSearch) {
await wait()
const filtered = events.filter((item) => {
const matchName = query.entityName ? item.entityName.includes(query.entityName) : true
const matchStatus = query.eventStatus === undefined ? true : item.eventStatus === query.eventStatus
return matchName && matchStatus
})
return page(filtered, query.pageNum, query.pageSize)
},
async createEvent(payload: CreateEventPayload) {
await wait()
events = [
{
id: `EVT${Date.now()}`,
...payload,
eventStatus: 0,
createByName: '当前员工',
showStatusDesc: '待生效'
},
...events
]
return true
},
async uploadFile(file: File) {
await wait()
return URL.createObjectURL(file)
}
}

40
src/api/orders.ts Normal file
View File

@@ -0,0 +1,40 @@
import http from './request'
import { mockApi } from './mock'
import { env } from '@/utils/env'
import type { PageResponse } from '@/types/api'
import { joinUrl } from '@/utils/url'
import type {
CommodityOrder,
CommodityOrderSearch,
UserOrderDetail,
WriteOffPayload,
WriteOffResult
} from '@/types/order'
export const fetchOrderList = async (query: CommodityOrderSearch): Promise<PageResponse<CommodityOrder>> => {
if (env.useMock) return mockApi.orderList(query)
return await http.post<PageResponse<CommodityOrder>>(
joinUrl(env.staffBase, 'order/commodityOrderList'),
query
) as unknown as PageResponse<CommodityOrder>
}
export const fetchOrderDetail = async (orderId: string): Promise<UserOrderDetail> => {
if (env.useMock) return mockApi.orderDetail(orderId)
return await http.post<UserOrderDetail>(
joinUrl(env.staffBase, 'order/userOrderDetail'),
{ orderId }
) as unknown as UserOrderDetail
}
export const writeOffOrder = async (payload: WriteOffPayload): Promise<WriteOffResult> => {
if (env.useMock) return mockApi.writeOff(payload)
await http.post<boolean>(joinUrl(env.staffBase, 'order/writeOff'), payload)
return {
success: true,
orderId: payload.orderId,
packageName: payload.packageName,
message: '核销成功',
operatedAt: new Date().toISOString()
}
}

36
src/api/request.ts Normal file
View File

@@ -0,0 +1,36 @@
import axios, { AxiosError } from 'axios'
import { showToast } from 'vant'
import { env } from '@/utils/env'
const http = axios.create({
baseURL: env.apiBase,
timeout: 15000
})
http.interceptors.request.use((config) => {
const token = localStorage.getItem('hotel_h5_access_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
http.interceptors.response.use(
(response) => {
const payload = response.data
if (payload && typeof payload === 'object' && 'code' in payload) {
if (payload.code === 0 || payload.code === 200) {
return payload.data === undefined ? payload : payload.data
}
return Promise.reject(new Error(payload.msg || '请求失败'))
}
return payload
},
(error: AxiosError<{ msg?: string }>) => {
const message = error.response?.data?.msg || error.message || '网络异常'
showToast(message)
return Promise.reject(error)
}
)
export default http

20
src/api/upload.ts Normal file
View File

@@ -0,0 +1,20 @@
import http from './request'
import { mockApi } from './mock'
import { env } from '@/utils/env'
import { joinUrl } from '@/utils/url'
interface UploadResult {
url?: string
}
export const uploadFile = async (file: File) => {
if (env.useMock) return mockApi.uploadFile(file)
const form = new FormData()
form.append('file', file)
const result = await http.post<string | UploadResult>(joinUrl(env.hotelBizBase, 'hotBizCommon/upload'), form, {
headers: {
'Content-Type': 'multipart/form-data'
}
}) as unknown as string | UploadResult
return typeof result === 'string' ? result : result.url || ''
}