Merge pull request 'develop' (#2) from develop into master
Reviewed-on: #2
This commit is contained in:
@@ -50,7 +50,7 @@
|
||||
|
||||
| Tab | 路由 | 说明 |
|
||||
|---|---|---|
|
||||
| 工作台 | `/home` | 展示关键数据和快捷入口 |
|
||||
| 工作台 | `/home` | 展示快捷入口和近期信息 |
|
||||
| 订单 | `/orders` | 查询订单、进入详情 |
|
||||
| 核销 | `/verify` | 手机摄像头扫码核销 |
|
||||
| 事件 | `/events` | 查看事件列表 |
|
||||
@@ -167,10 +167,6 @@
|
||||
|
||||
- 当前日期
|
||||
- 当前账号
|
||||
- 待使用订单数
|
||||
- 待确认订单数
|
||||
- 生效事件数
|
||||
- 今日订单数
|
||||
- 快捷入口:扫码核销、订单列表、发布事件、事件列表
|
||||
- 近期订单
|
||||
- 近期事件
|
||||
|
||||
18
README.md
18
README.md
@@ -124,6 +124,24 @@ docker run -d \
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
测试环境使用独立 compose 文件:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.test.yml up -d --build
|
||||
```
|
||||
|
||||
测试环境默认后端网关是:
|
||||
|
||||
```bash
|
||||
BACKEND_GATEWAY=http://8.138.234.141/ingress
|
||||
```
|
||||
|
||||
如果测试服务器上的网关地址不同,可以启动时覆盖:
|
||||
|
||||
```bash
|
||||
BACKEND_GATEWAY=http://测试网关地址 H5_PORT=10086 docker compose -f docker-compose.test.yml up -d --build
|
||||
```
|
||||
|
||||
`BACKEND_GATEWAY` 是容器内 Nginx 代理到的后端网关地址,不要以 `/` 结尾。生产环境如果前面还有宿主机 Nginx,宿主机 Nginx 只需要负责域名、HTTPS 证书,并把流量转发到 `127.0.0.1:10086`。
|
||||
|
||||
生产扫码建议通过 HTTPS 访问,否则部分移动浏览器或微信内置浏览器可能无法打开摄像头。
|
||||
|
||||
13
docker-compose.test.yml
Normal file
13
docker-compose.test.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
services:
|
||||
hotel-biz-h5-test:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
BUILD_MODE: test
|
||||
image: hotel-biz-h5:test
|
||||
container_name: hotel-biz-h5-test
|
||||
environment:
|
||||
BACKEND_GATEWAY: ${BACKEND_GATEWAY:-http://8.138.234.141/ingress}
|
||||
ports:
|
||||
- "${H5_PORT:-10086}:10086"
|
||||
restart: unless-stopped
|
||||
@@ -34,6 +34,7 @@
|
||||
- 如果直接把 `VITE_API_BASE_URL` 配成后端网关 IP 或域名,跨域就需要后端网关正确返回 CORS 头;这不是前端代码能完全解决的。
|
||||
- SPA 路由需要 Nginx `location / { try_files $uri $uri/ /index.html; }`。
|
||||
- Docker 部署时,容器内 Nginx 负责静态资源和接口前缀代理,运行时通过 `BACKEND_GATEWAY` 环境变量指定后端网关地址。
|
||||
- 测试环境使用 `docker-compose.test.yml`,构建参数为 `BUILD_MODE=test`,默认测试网关可通过启动时的 `BACKEND_GATEWAY` 覆盖。
|
||||
- 如果生产服务器外层还有 Nginx,外层 Nginx 只负责域名、HTTPS 和转发到容器端口;接口前缀代理继续交给容器内 Nginx。
|
||||
|
||||
## 移动端布局
|
||||
|
||||
@@ -259,40 +259,6 @@ canvas {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
min-height: 92px;
|
||||
padding: 14px;
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
margin-top: 8px;
|
||||
color: var(--text-strong);
|
||||
font-size: 25px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-hint {
|
||||
margin-top: 8px;
|
||||
color: var(--primary-deep);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.order-card,
|
||||
.event-card {
|
||||
display: block;
|
||||
@@ -461,8 +427,4 @@ canvas {
|
||||
.quick-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.stat-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { RouterLink, useRouter } from 'vue-router'
|
||||
import {
|
||||
CalendarDays,
|
||||
@@ -27,10 +27,6 @@ const orders = ref<CommodityOrder[]>([])
|
||||
const events = ref<EventData[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
const todoOrders = computed(() => orders.value.filter((item) => item.orderStatus === '2'))
|
||||
const confirmOrders = computed(() => orders.value.filter((item) => item.orderStatus === '1'))
|
||||
const activeEvents = computed(() => events.value.filter((item) => item.showStatusDesc === '生效中'))
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -53,7 +49,7 @@ onMounted(loadData)
|
||||
<header class="home-hero">
|
||||
<div>
|
||||
<p class="page-kicker">{{ today() }}</p>
|
||||
<h1 class="page-title">员工工作台</h1>
|
||||
<h1 class="page-title">工作台</h1>
|
||||
<p class="page-subtitle">{{ auth.user?.phone || auth.user?.username || '当前员工' }}</p>
|
||||
</div>
|
||||
<van-button type="primary" size="small" @click="router.push('/verify')">
|
||||
@@ -62,29 +58,6 @@ onMounted(loadData)
|
||||
</van-button>
|
||||
</header>
|
||||
|
||||
<section class="stat-grid section">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">待使用订单</div>
|
||||
<div class="stat-value">{{ todoOrders.length }}</div>
|
||||
<div class="stat-hint">可进入详情核销</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">待确认订单</div>
|
||||
<div class="stat-value">{{ confirmOrders.length }}</div>
|
||||
<div class="stat-hint">需要员工处理</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">生效事件</div>
|
||||
<div class="stat-value">{{ activeEvents.length }}</div>
|
||||
<div class="stat-hint">展示给员工</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">今日订单</div>
|
||||
<div class="stat-value">{{ orders.length }}</div>
|
||||
<div class="stat-hint">默认近期待办</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<h2 class="section-title">快捷入口</h2>
|
||||
<div class="quick-grid">
|
||||
@@ -109,7 +82,7 @@ onMounted(loadData)
|
||||
|
||||
<section class="section">
|
||||
<h2 class="section-title">
|
||||
待处理订单
|
||||
近期订单
|
||||
<small @click="router.push('/orders')">全部</small>
|
||||
</h2>
|
||||
<van-loading v-if="loading" />
|
||||
|
||||
@@ -7,7 +7,8 @@ import StatusTag from '@/components/StatusTag.vue'
|
||||
import type { CommodityOrder, CommodityOrderSearch } from '@/types/order'
|
||||
import { commodityTypeText } from '@/utils/constants'
|
||||
|
||||
const loading = ref(false)
|
||||
const listLoading = ref(false)
|
||||
const requesting = ref(false)
|
||||
const refreshing = ref(false)
|
||||
const finished = ref(false)
|
||||
const records = ref<CommodityOrder[]>([])
|
||||
@@ -19,25 +20,31 @@ const query = reactive<CommodityOrderSearch>({
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
const buildSearchQuery = (): CommodityOrderSearch => ({
|
||||
...query,
|
||||
contactPhone: keyword.value.trim(),
|
||||
orderStatus: String(status.value || '')
|
||||
})
|
||||
|
||||
const loadOrders = async (reset = false) => {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
if (requesting.value) {
|
||||
refreshing.value = false
|
||||
return
|
||||
}
|
||||
requesting.value = true
|
||||
listLoading.value = true
|
||||
if (reset) {
|
||||
query.pageNum = 1
|
||||
finished.value = false
|
||||
}
|
||||
try {
|
||||
const searchText = keyword.value.trim()
|
||||
const page = await fetchOrderList({
|
||||
...query,
|
||||
orderStatus: status.value || undefined,
|
||||
contactPhone: searchText || undefined
|
||||
})
|
||||
const page = await fetchOrderList(buildSearchQuery())
|
||||
records.value = reset ? page.records : [...records.value, ...page.records]
|
||||
finished.value = records.value.length >= page.total || page.records.length === 0
|
||||
query.pageNum += 1
|
||||
} finally {
|
||||
loading.value = false
|
||||
requesting.value = false
|
||||
listLoading.value = false
|
||||
refreshing.value = false
|
||||
}
|
||||
}
|
||||
@@ -80,7 +87,7 @@ onMounted(() => loadOrders(true))
|
||||
</van-tabs>
|
||||
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多订单" @load="loadOrders">
|
||||
<van-list v-model:loading="listLoading" :finished="finished" finished-text="没有更多订单" @load="loadOrders">
|
||||
<div v-if="records.length" class="order-list">
|
||||
<RouterLink v-for="order in records" :key="order.id" class="order-card" :to="`/orders/${order.id}`">
|
||||
<div class="order-card__main">
|
||||
@@ -105,7 +112,7 @@ onMounted(() => loadOrders(true))
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div v-else-if="!loading" class="empty-state">
|
||||
<div v-else-if="!listLoading" class="empty-state">
|
||||
<ClipboardList :size="30" />
|
||||
<div>暂无订单</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user