优化测试部署和页面展示细节

This commit is contained in:
andy
2026-07-01 12:19:40 +08:00
parent 9e67339d3c
commit 5ac547a9d5
7 changed files with 143 additions and 23 deletions

View File

@@ -1,4 +1,5 @@
VITE_USE_MOCK=false
VITE_APP_BASE=/
VITE_PROXY_TARGET=
# VITE_API_BASE_URL=http://8.138.234.141/ingress
VITE_API_BASE_URL=

View File

@@ -20,6 +20,21 @@ server {
try_files $uri =404;
}
location ^~ /hotel-staff-h5/assets/ {
rewrite ^/hotel-staff-h5/(.*)$ /$1 break;
expires 30d;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
location = /hotel-staff-h5 {
return 301 /hotel-staff-h5/;
}
location ^~ /hotel-staff-h5/ {
try_files $uri $uri/ /index.html;
}
location /auth/ {
proxy_pass ${BACKEND_GATEWAY}/auth/;
proxy_http_version 1.1;

View File

@@ -8,6 +8,11 @@ const http = axios.create({
timeout: 15000
})
export type RequestError = Error & {
cause?: unknown
toastShown?: boolean
}
let isRedirectingToLogin = false
const authExpiredTexts = ['请求令牌已过期', '令牌已过期', 'token已过期', 'Token已过期', 'invalid_token']
@@ -20,6 +25,13 @@ const getHeader = (headers: unknown, key: string) => {
const hasBearerAuthorization = (headers: unknown) => getHeader(headers, 'Authorization').startsWith('Bearer ')
const createRequestError = (message: string, cause?: unknown, toastShown = false) => {
const requestError = new Error(message) as RequestError
requestError.cause = cause
requestError.toastShown = toastShown
return requestError
}
const isAuthExpired = (status?: number, code?: unknown, message = '', hasBearerToken = false) => {
const normalizedCode = String(code || '')
return (
@@ -32,7 +44,7 @@ const redirectToLogin = (message: string) => {
clearAuthStorage()
window.dispatchEvent(new Event(AUTH_EXPIRED_EVENT))
if (isRedirectingToLogin || window.location.pathname.endsWith('/login')) return
if (isRedirectingToLogin || window.location.pathname.endsWith('/login')) return false
isRedirectingToLogin = true
showToast(message || '登录已失效,请重新登录')
@@ -46,6 +58,7 @@ const redirectToLogin = (message: string) => {
}
})
})
return true
}
http.interceptors.request.use((config) => {
@@ -65,9 +78,10 @@ http.interceptors.response.use(
}
const message = payload.msg || payload.message || '请求失败'
if (isAuthExpired(response.status, payload.code, message, hasBearerAuthorization(response.config.headers))) {
redirectToLogin(message)
const toastShown = redirectToLogin(message)
return Promise.reject(createRequestError(message, undefined, toastShown))
}
return Promise.reject(new Error(message))
return Promise.reject(createRequestError(message))
}
return payload
},
@@ -81,11 +95,11 @@ http.interceptors.response.use(
hasBearerAuthorization(error.config?.headers)
)
) {
redirectToLogin(message)
return Promise.reject(error)
const toastShown = redirectToLogin(message)
return Promise.reject(createRequestError(message, error, toastShown))
}
showToast(message)
return Promise.reject(error)
return Promise.reject(createRequestError(message, error, true))
}
)

View File

@@ -53,6 +53,7 @@ onMounted(() => loadEvents(true))
<div>
<p class="page-kicker">事件管理</p>
<h1 class="page-title">事件列表</h1>
<p class="page-subtitle">查看门店通知弹窗提醒和正在生效的运营事件</p>
</div>
<van-button type="primary" size="small" @click="router.push('/events/create')">
<template #icon><Plus :size="16" /></template>
@@ -96,6 +97,9 @@ onMounted(() => loadEvents(true))
</div>
</article>
</div>
<div v-else-if="loading" class="list-skeleton">
<van-skeleton v-for="item in 3" :key="item" title :row="4" />
</div>
<div v-else-if="!loading" class="empty-state">
<Megaphone :size="30" />
<div>暂无事件</div>
@@ -117,10 +121,24 @@ onMounted(() => loadEvents(true))
padding-top: 12px;
}
.list-skeleton {
display: grid;
gap: 10px;
padding-top: 12px;
}
.list-skeleton :deep(.van-skeleton) {
padding: 13px;
border: 1px solid rgba(202, 214, 208, 0.82);
border-radius: var(--panel-radius);
background: rgba(255, 255, 255, 0.62);
}
.event-card__media {
height: 124px;
overflow: hidden;
margin-bottom: 10px;
border: 1px solid rgba(221, 230, 225, 0.9);
border-radius: var(--radius);
background: var(--surface-soft);
}
@@ -133,10 +151,14 @@ onMounted(() => loadEvents(true))
}
.event-desc {
min-width: 0;
margin: 10px 0;
color: var(--text-main);
font-size: 14px;
line-height: 1.55;
overflow-wrap: anywhere;
white-space: normal;
word-break: break-word;
}
.event-status-row {

View File

@@ -4,6 +4,7 @@ import { useRoute, useRouter } from 'vue-router'
import { showToast } from 'vant'
import { BadgeCheck, Phone, ShieldCheck } from 'lucide-vue-next'
import { getImageCodeUrl, sendMobileCode } from '@/api/auth'
import type { RequestError } from '@/api/request'
import { useAuthStore } from '@/stores/auth'
const router = useRouter()
@@ -51,6 +52,11 @@ const startCountdown = () => {
}, 1000)
}
const showErrorToast = (error: unknown, fallback: string) => {
if (error && typeof error === 'object' && (error as RequestError).toastShown) return
showToast(error instanceof Error ? error.message : fallback)
}
const handleSend = async () => {
if (!/^1\d{10}$/.test(phone.value)) {
showToast('请输入正确手机号')
@@ -78,7 +84,7 @@ const handleSend = async () => {
}
startCountdown()
} catch (error) {
showToast(error instanceof Error ? error.message : '验证码发送失败')
showErrorToast(error, '验证码发送失败')
refreshImageCode()
} finally {
sending.value = false
@@ -100,7 +106,7 @@ const handleLogin = async () => {
const redirect = typeof route.query.redirect === 'string' ? route.query.redirect : '/home'
await router.replace(redirect)
} catch (error) {
showToast(error instanceof Error ? error.message : '登录失败')
showErrorToast(error, '登录失败')
} finally {
loading.value = false
}

View File

@@ -29,8 +29,8 @@ const logout = () => {
<template #icon><ShieldCheck :size="18" class="cell-icon" /></template>
</van-cell>
<van-cell title="组织绑定" :value="auth.hasOrganization ? '已绑定' : '未绑定'" />
<van-cell title="组织ID" :value="auth.memberInfo?.organizationId || '-'" />
<van-cell title="员工ID" :value="auth.memberInfo?.memberId || '-'" />
<van-cell class="id-cell" title="组织ID" :value="auth.memberInfo?.organizationId || '-'" />
<van-cell class="id-cell" title="员工ID" :value="auth.memberInfo?.memberId || '-'" />
<van-cell title="租户" :value="String(auth.user?.tenantId || '-')" />
</section>
@@ -48,13 +48,17 @@ const logout = () => {
gap: 12px;
padding: 18px 14px;
margin-top: 18px;
background:
linear-gradient(135deg, rgba(19, 144, 111, 0.12), rgba(237, 246, 255, 0.72)),
var(--surface);
}
.avatar {
display: grid;
width: 58px;
height: 58px;
border-radius: var(--radius);
border: 1px solid rgba(19, 144, 111, 0.16);
border-radius: var(--panel-radius);
background: var(--primary-soft);
color: var(--primary-deep);
place-items: center;
@@ -76,6 +80,33 @@ const logout = () => {
margin-top: 12px;
}
.mine-panel :deep(.van-cell) {
align-items: center;
background: transparent;
}
.mine-panel :deep(.van-cell__title) {
flex: 0 0 86px;
color: var(--text-main);
font-weight: 650;
}
.mine-panel :deep(.van-cell__value) {
min-width: 0;
color: var(--text-muted);
text-align: right;
}
.mine-panel :deep(.id-cell .van-cell__value) {
flex: 1;
overflow: hidden;
font-size: 13px;
font-variant-numeric: tabular-nums;
text-overflow: ellipsis;
white-space: nowrap;
word-break: keep-all;
}
.cell-icon {
margin: 2px 8px 0 0;
color: var(--primary);
@@ -83,5 +114,6 @@ const logout = () => {
.logout-button {
margin-top: 14px;
background: rgba(255, 255, 255, 0.78);
}
</style>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { CalendarDays, CheckCircle2, Phone, ReceiptText, UserRound } from 'lucide-vue-next'
import { CalendarDays, CheckCircle2, ReceiptText, UserRound } from 'lucide-vue-next'
import { fetchOrderDetail } from '@/api/orders'
import PageNav from '@/components/PageNav.vue'
import StatusTag from '@/components/StatusTag.vue'
@@ -82,10 +82,6 @@ onMounted(loadDetail)
<span>订单金额</span>
<strong>¥{{ detail.orderAmt }}</strong>
</div>
<div class="detail-row">
<span>优惠金额</span>
<strong>¥{{ detail.discountAmt || '0.00' }}</strong>
</div>
<div class="detail-row">
<span>支付流水</span>
<strong>{{ detail.paySerialNumber || '-' }}</strong>
@@ -103,11 +99,6 @@ onMounted(loadDetail)
<span>预约时间</span>
<strong>{{ detail.reservationDate || detail.checkInData || '无需预约' }}</strong>
</div>
<div class="info-row">
<Phone :size="17" />
<span>投诉电话</span>
<strong>{{ detail.complaintHotline || '-' }}</strong>
</div>
</section>
<section class="panel detail-panel">
@@ -130,7 +121,8 @@ onMounted(loadDetail)
<div>
<strong>{{ record.commodityName || detail.commodityName }}</strong>
<p v-if="record.packageName">套餐名称{{ record.packageName }}</p>
<p>{{ record.createTime }} · {{ record.userName || '员工' }}</p>
<p>核销时间{{ record.createTime || '-' }}</p>
<p>核销人{{ record.userName || '-' }}</p>
</div>
</div>
</div>
@@ -159,7 +151,10 @@ onMounted(loadDetail)
display: grid;
grid-template-columns: 112px minmax(0, 1fr);
gap: 12px;
padding: 12px;
padding: 13px;
background:
linear-gradient(135deg, rgba(19, 144, 111, 0.08), rgba(237, 246, 255, 0.58)),
var(--surface);
}
.order-hero__content,
@@ -171,6 +166,7 @@ onMounted(loadDetail)
.order-hero img {
width: 112px;
height: 112px;
border: 1px solid rgba(221, 230, 225, 0.9);
border-radius: var(--radius);
object-fit: cover;
}
@@ -181,6 +177,7 @@ onMounted(loadDetail)
margin: 12px 0;
color: var(--text-strong);
font-size: 18px;
font-weight: 750;
line-height: 1.35;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
@@ -205,6 +202,16 @@ onMounted(loadDetail)
font-size: 14px;
}
.package-row,
.record-row {
padding: 10px 0;
}
.package-row + .package-row,
.record-row + .record-row {
border-top: 1px solid rgba(221, 230, 225, 0.8);
}
.info-row svg,
.package-row svg,
.record-row svg {
@@ -234,4 +241,27 @@ onMounted(loadDetail)
font-size: 13px;
line-height: 1.5;
}
.empty-line {
padding: 12px;
border-radius: var(--radius);
background: var(--surface-soft);
text-align: center;
}
@media (max-width: 360px) {
.order-hero {
grid-template-columns: 92px minmax(0, 1fr);
}
.order-hero img {
width: 92px;
height: 92px;
}
.order-hero h1 {
margin: 8px 0;
font-size: 16px;
}
}
</style>