feat(order): add order detail and list pages with components for order management

- Implemented order detail page with components for displaying order status, user info, and refund options.
- Created order list page with pagination and order cards for displaying all orders.
- Added styles for order detail and list pages.
- Developed a prompt document outlining component requirements for the order management system.
- Introduced a new Card component for quick booking with a responsive design.
- Enhanced Tabs component for better navigation between different categories.
- Integrated z-paging for efficient data loading and management in order and quick booking lists.
- Added service order card component for displaying service requests with call functionality.
- Updated main CSS for improved viewport handling.
This commit is contained in:
duanshuwen
2026-05-26 15:38:33 +08:00
parent fa76435e38
commit ad93ca5e8e
194 changed files with 17069 additions and 2 deletions

View File

@@ -0,0 +1,146 @@
<template>
<div class="tab-container relative">
<div class="tab-wrapper flex flex-items-center">
<div
v-for="(item, index) in tabList"
:key="item.id"
:class="[
'tab-item flex flex-items-center flex-justify-center relative',
activeIndex === index && 'tab-item-active',
]"
@click="handleTabClick(index)"
>
<div class="tab-item-inner flex flex-items-center">
<uni-icons
:class="['icon mr-4', activeIndex === index && 'icon-active']"
fontFamily="znicons"
size="20"
color="opacity"
>
{{ zniconsMap[item.iconCode] }}
</uni-icons>
<span
:class="[
'font-size-16 font-500 color-525866 ',
activeIndex === index && 'tab-span-active',
]"
>
{{ item.iconTitle }}
</span>
</div>
<!-- 每项内的下划线指示器通过类控制显示/隐藏 -->
<div
class="tab-item-indicator"
:class="{ visible: activeIndex === index }"
></div>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref, watch } from "vue";
import { zniconsMap } from "@/static/fonts/znicons";
import { commodityTypePageList } from "@/request/api/GoodsApi";
// Props
const props = defineProps({
tabs: {
type: Array,
default: () => [
{ iconTitle: "客房", typeCode: "0", iconCode: "zn-nav-room" },
{ iconTitle: "门票", typeCode: "1", iconCode: "zn-nav-ticket" },
{ iconTitle: "餐食", typeCode: "2", iconCode: "zn-nav-meal" },
{ iconTitle: "套餐", typeCode: "3", iconCode: "zn-package" },
{ iconTitle: "文创", typeCode: "4", iconCode: "zn-package" },
],
},
defaultActive: {
type: Number,
default: 0,
},
indicatorColor: {
type: String,
default: "#1890ff",
},
});
// Emits
const emit = defineEmits(["change", "update:modelValue"]);
// 响应式数据
const activeIndex = ref(props.defaultActive);
const tabList = ref([]);
// 处理Tab点击
const handleTabClick = (index) => {
changeTabItem(index);
};
// 支持 force 参数,强制触发(即使 index 与当前相同)
const changeTabItem = (index, force = false) => {
if (!force && activeIndex.value === index) return;
activeIndex.value = index;
emit("change", {
index,
item: tabList.value[index],
});
emit("update:modelValue", index);
};
// 监听tabs变化
watch(
() => props.tabs,
(newTabs) => {
tabList.value = newTabs;
},
{ deep: true },
);
// 监听defaultActive变化
watch(
() => props.defaultActive,
(newActive) => {
if (newActive !== activeIndex.value) {
activeIndex.value = newActive;
}
},
);
// 暴露方法
defineExpose({
setActiveIndex: (index) => {
if (index >= 0 && index < tabList.value.length) {
handleTabClick(index);
}
},
getActiveIndex: () => activeIndex.value,
getActiveItem: () => tabList.value[activeIndex.value],
});
onMounted(() => {
getCommodityTypePageList();
});
// 获取商品类型列表(示例方法,实际使用时根据需要调用)
const getCommodityTypePageList = async () => {
const res = await commodityTypePageList({ size: 20, current: 1 });
if (res && res.data && res.data.records) {
tabList.value = res.data.records;
} else {
tabList.value = props.tabs;
}
// 加载完成后强制选中第一个项并通知外部
if (tabList.value && tabList.value.length > 0) {
changeTabItem(0, true);
} else {
changeTabItem(props.defaultActive, true);
}
};
</script>
<style lang="scss" scoped>
@import "./styles/index.scss";
</style>

View File

@@ -0,0 +1,90 @@
.tab-wrapper {
background-color: $theme-color-100;
height: 48px;
overflow-x: auto;
/* 支持横向滚动 */
-webkit-overflow-scrolling: touch;
/* 平滑滚动(移动端) */
white-space: nowrap;
/* 防止换行 */
justify-content: flex-start;
/* 覆盖工具类,靠左排列以便滚动 */
}
.tab-item {
height: 100%;
flex: 0 0 auto;
/* 不让子项拉伸,按内容宽度排列 */
padding: 0 12px;
/* 增加横向间距,便于触控 */
min-width: 68px;
/* 保证可点击区域 */
}
.icon {
height: 20px;
width: 20px;
color: #525866;
}
.icon-active {
color: $theme-color-500;
}
/* 组件模板中使用了绝对定位的内部元素,为保证父元素宽度基于内容,重置该子元素为静态布局 */
.tab-item>.absolute {
position: static !important;
display: flex;
align-items: center;
}
.tab-item-active {
&::before {
content: "";
position: absolute;
left: 4px;
top: 0;
right: 4px;
bottom: 0;
background-color: #fff;
border-radius: 20px 20px 0 0;
transform: perspective(40px) rotateX(6deg) translate(0, -1px);
transform-origin: bottom bottom;
box-shadow: 0 -0.5px 0 $theme-color-500;
}
}
.tab-text-active {
color: $theme-color-500;
z-index: 3;
}
/* 已改为每项内部指示器,移除了全局指示器样式 */
/* 每项内的指示器(替代全局指示器) */
.tab-item-inner {
display: inline-flex;
align-items: center;
position: relative;
z-index: 3;
/* 确保内容icon/text位于 .tab-item-active::before 之上 */
}
.tab-item-indicator {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%) scaleX(0.9);
height: 3px;
width: 24px;
background-color: $theme-color-500;
border-radius: 3px 3px 0 0;
opacity: 0;
transition: opacity 0.2s ease, transform 0.2s ease;
z-index: 3;
}
.tab-item-indicator.visible {
opacity: 1;
transform: translateX(-50%) scaleX(1);
}