feat: 调整项目结构

This commit is contained in:
duanshuwen
2025-09-21 17:25:09 +08:00
parent 0b66462d16
commit 9f23854ad5
410 changed files with 3806 additions and 1668 deletions

View File

@@ -0,0 +1,45 @@
<template>
<view class="info-row">
<text class="label">{{ label }}</text>
<text class="value">{{ value }}</text>
</view>
</template>
<script setup>
import { defineProps } from "vue";
// Props
const props = defineProps({
label: {
type: String,
required: true,
default: "",
},
value: {
type: [String, Number],
required: true,
default: "",
},
});
</script>
<style scoped lang="scss">
.info-row {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.label {
font-size: 12px;
color: #666666;
flex-shrink: 0;
margin-right: 8px;
}
.value {
font-size: 14px;
color: #333333;
flex: 1;
}
</style>

View File

@@ -0,0 +1,116 @@
<template>
<view class="card-content">
<!-- 动态渲染信息行 -->
<InfoRow
v-for="item in displayItems"
:key="item.label"
:label="item.label"
:value="item.value"
/>
</view>
</template>
<script setup>
import { defineProps, computed } from "vue";
import InfoRow from "./InfoRow.vue";
// 订单类型常量
const ORDER_TYPES = {
HOTEL: "0", // 酒店订单
TICKET: "1", // 门票订单
OTHER: "2", // 其他订单
};
// 标签常量
const LABELS = {
ORDER_ID: "订单编号",
CHECK_IN_TIME: "入住时间",
VISITOR_NAME: "游客姓名",
CONTACT_PHONE: "联系电话",
QUANTITY: "份数",
CREATE_TIME: "创建时间",
CONTACT_GUEST: "联系房客",
};
// Props
const props = defineProps({
orderData: {
type: Object,
required: true,
default: () => ({
orderType: undefined,
orderId: "",
checkInTime: "",
visitorName: "",
contactPhone: "",
userName: "",
userPhone: "",
commodityAmount: 0,
createTime: "",
}),
},
});
// 格式化份数
const formatQuantity = (amount) => {
return `${Math.floor(amount || 0)}`;
};
// 计算显示项目
const displayItems = computed(() => {
const { orderData } = props;
const { orderType } = orderData;
// 工单情况orderType 为 undefined
if (orderType === undefined) {
return [
{ label: LABELS.CREATE_TIME, value: orderData.createTime },
{ label: LABELS.CONTACT_GUEST, value: orderData.userName },
{ label: LABELS.CONTACT_PHONE, value: orderData.userPhone },
];
}
// 订单情况:根据 orderType 返回不同的显示项
switch (orderType) {
case ORDER_TYPES.HOTEL:
return [
{ label: LABELS.ORDER_ID, value: orderData.orderId },
{
label: LABELS.CHECK_IN_TIME,
value:
orderData.checkInData?.replace(
/(\d{4})-(\d{1,2})-(\d{1,2})/,
"$2月$3日"
) +
"-" +
orderData.checkOutData?.replace(
/(\d{4})-(\d{1,2})-(\d{1,2})/,
"$2月$3日"
),
},
{ label: LABELS.VISITOR_NAME, value: orderData.visitorName },
{ label: LABELS.CONTACT_PHONE, value: orderData.contactPhone },
];
case ORDER_TYPES.TICKET:
case ORDER_TYPES.OTHER:
return [
{ label: LABELS.ORDER_ID, value: orderData.orderId },
{
label: LABELS.QUANTITY,
value: formatQuantity(orderData.commodityAmount),
},
];
default:
// 兜底情况,返回空数组
return [];
}
});
</script>
<style scoped lang="scss">
.card-content {
padding: 16px;
}
</style>

View File

@@ -0,0 +1,109 @@
# OrderCard 组件
订单卡片组件,用于显示订单和工单信息。
## 组件结构
```
OrderCard/
├── index.vue # 主组件
├── OrderCardContent.vue # 卡片内容组件
├── InfoRow.vue # 信息行组件
├── images/ # 图片资源
├── styles/ # 样式文件
└── README.md # 说明文档
```
## 组件说明
### OrderCard (主组件)
- 负责整体布局和事件处理
- 包含卡片头部、分割线、内容区域和操作区域
- 处理点击、呼叫等交互事件
### OrderCardContent (内容组件)
- 负责根据 `orderType` 动态渲染不同的内容
- 支持订单类型0-酒店订单1-门票订单2-其他订单)和工单类型
- 使用条件渲染显示对应的信息字段
### InfoRow (信息行组件)
- 可复用的信息展示组件
- 统一的标签和值的显示格式
- 支持字符串和数字类型的值
## 使用方式
```vue
<template>
<OrderCard
:order-data="orderData"
@click="handleCardClick"
@call="handleCall"
@complete="handleComplete"
/>
</template>
<script setup>
import OrderCard from '@/pages/order/components/OrderCard/index.vue'
const orderData = {
id: '123',
commodityName: '订单名称',
orderType: 0, // 0-酒店订单, 1-门票订单, 2-其他订单, undefined-工单
orderNumber: 'ORD123456',
checkInTime: '2024-01-01',
visitorName: '张三',
contactPhone: '13800138000',
quantity: 2,
createTime: '2024-01-01 10:00:00',
orderStatus: '1',
status: 'pending'
}
</script>
```
## Props
### orderData (Object)
| 字段 | 类型 | 说明 | 必填 |
|------|------|------|------|
| id | String | 订单ID | 是 |
| commodityName | String | 商品名称 | 是 |
| orderType | Number/undefined | 订单类型0-酒店订单1-门票订单2-其他订单undefined-工单 | 否 |
| orderNumber | String | 订单编号 | 否 |
| checkInTime | String | 入住时间orderType=0时使用 | 否 |
| visitorName | String | 游客姓名/联系房客 | 否 |
| contactPhone | String | 联系电话 | 否 |
| quantity | Number | 份数orderType=1,2时使用 | 否 |
| createTime | String | 创建时间(工单时使用) | 否 |
| orderStatus | String | 订单状态 | 否 |
| status | String | 状态 | 否 |
## Events
| 事件名 | 说明 | 参数 |
|--------|------|------|
| click | 卡片点击事件 | orderData |
| call | 呼叫事件 | orderData |
| complete | 完成事件 | orderData |
## 显示逻辑
### 订单类型 (orderType !== undefined)
- **orderType = 0 (酒店订单)**:显示订单编号、入住时间、游客姓名、联系电话
- **orderType = 1 (门票订单)**:显示订单编号、份数
- **orderType = 2 (其他订单)**:显示订单编号、份数
### 工单类型 (orderType === undefined)
显示创建时间、联系房客、联系电话
## 优势
1. **可读性**:组件职责单一,代码结构清晰
2. **可维护性**:组件化拆分,便于独立维护和测试
3. **可复用性**InfoRow 组件可在其他地方复用
4. **健壮性**:类型检查和默认值处理
5. **扩展性**:新增订单类型只需修改 OrderCardContent 组件

View File

@@ -0,0 +1,59 @@
// 订单类型
export const ORDER_TYPE_MAP = {
0: [
{
label: '订单编号',
key: 'orderId'
},
{
label: '入住时间',
key: 'checkInData'
},
{
label: '游客姓名',
key: 'visitorName'
},
{
label: '联系电话',
key: 'contactPhone'
}
],
1: [
{
label: '订单编号',
key: 'orderId'
},
{
label: '份数',
key: 'commodityAmount'
}
],
2: [
{
label: '订单编号',
key: 'orderId'
},
{
label: '份数',
key: 'commodityAmount'
}
],
}
// 工单类型
export const SERVICE_TYPE_MAP = {
0: [
{
label: '创建时间',
key: 'createTime'
},
{
label: '联系房客',
key: 'userName'
},
{
label: '联系电话',
key: 'userPhone'
}
],
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

View File

@@ -0,0 +1,121 @@
<template>
<view class="order-card" @click="handleCardClick">
<!-- 卡片头部 -->
<view class="card-header">
<view class="status-info">
<image class="status-icon" :src="getStatusIcon()"></image>
<view class="order-title">
{{ orderData.workOrderTypeName || orderData.commodityName }}
</view>
<image
v-if="props.orderData.orderType !== undefined"
class="arrow-icon"
src="./images/arrow.png"
></image>
</view>
<view
v-if="orderData.status !== 'pending'"
:class="[
'status-tag',
`tag-${orderData.orderStatus || orderData.workOrderStatus}`,
]"
>
{{ getStatusText(orderData.orderStatus || orderData.workOrderStatus) }}
</view>
</view>
<!-- 分割线 -->
<Divider />
<!-- 卡片内容 -->
<OrderCardContent :order-data="orderData" />
</view>
</template>
<script setup>
import { defineProps, defineExpose } from "vue";
import Divider from "@/components/Divider/index.vue";
import OrderCardContent from "./OrderCardContent.vue";
import serviceIcon from "./images/service.png";
import ticketIcon from "./images/ticket.png";
import hotelIcon from "./images/hotel.png";
import foodIcon from "./images/food.png";
// Props
const props = defineProps({
orderData: {
type: Object,
required: true,
default: () => ({
id: "",
workOrderTypeName: "",
createTime: "",
contactName: "",
contactPhone: "",
orderStatus: "0", // pending-待处理, completed-已完成, cancelled-已取消
orderType: undefined, // 0-酒店订单, 1-门票订单, 2-其他订单, undefined-工单
orderNumber: "", // 订单编号
checkInTime: "", // 入住时间
visitorName: "", // 游客姓名
commodityAmount: 0, // 份数
workOrderStatus: 0, // 工单状态0-待处理, 1-已完成
}),
},
});
// Emits
const emit = defineEmits(["click", "call"]);
// 图标映射
const ICON_MAP = {
0: hotelIcon, // 酒店订单
1: ticketIcon, // 门票订单
2: foodIcon, // 其他订单
};
// 获取状态图标
const getStatusIcon = () => {
// 工单情况orderType 为 undefined
if (props.orderData.orderType === undefined) {
return serviceIcon;
}
// 订单情况:根据 orderType 返回对应图标
return ICON_MAP[props.orderData.orderType] || serviceIcon;
};
// 获取状态文本
const getStatusText = (status) => {
// 工单情况orderType 为 undefined
if (props.orderData.orderType === undefined) {
const workOrderStatusMap = {
0: "待接单",
1: "处理中",
2: "已完成",
3: "已关闭",
};
return workOrderStatusMap[status] || "未知状态";
}
// 订单情况orderType 有值
const orderStatusMap = {
0: "待支付",
1: "待确认",
2: "待使用",
3: "已取消",
4: "退款中",
5: "已退款",
6: "已完成",
};
return orderStatusMap[status] || "未知状态";
};
// 处理卡片点击
const handleCardClick = () => {
if (props.orderData.orderType === undefined) return;
emit("click", props.orderData);
};
</script>
<style scoped lang="scss">
@use "./styles/index.scss";
</style>

View File

@@ -0,0 +1,170 @@
# OrderCard 工单卡片组件
## 组件概述
OrderCard 是一个用于显示工单信息的卡片组件,支持多种工单状态展示、操作按钮和自定义内容。适用于工单管理、客服系统等场景。
## 功能特性
- ✅ 多种工单状态支持(待处理、处理中、已完成、已取消)
- ✅ 状态图标和标签显示
- ✅ 工单基本信息展示
- ✅ 可配置的操作按钮
- ✅ 自定义操作区域插槽
- ✅ 点击事件和操作事件
- ✅ 响应式设计
- ✅ 暗色模式支持
- ✅ 优雅的交互动画
## 组件属性 (Props)
| 属性名 | 类型 | 默认值 | 必填 | 说明 |
|--------|------|--------|------|------|
| orderData | Object | - | 是 | 工单数据对象 |
| showActions | Boolean | true | 否 | 是否显示操作按钮区域 |
### orderData 对象结构
```javascript
{
id: String, // 工单ID
title: String, // 工单标题
createTime: String, // 创建时间
contactName: String, // 联系人姓名
contactPhone: String, // 联系电话
status: String // 工单状态pending/processing/completed/cancelled
}
```
## 组件事件 (Events)
| 事件名 | 参数 | 说明 |
|--------|------|------|
| click | orderData | 卡片点击事件 |
| call | orderData | 呼叫按钮点击事件 |
| complete | orderData | 完成按钮点击事件 |
## 插槽 (Slots)
| 插槽名 | 说明 |
|--------|------|
| actions | 自定义操作按钮区域 |
## 工单状态说明
| 状态值 | 显示文本 | 图标颜色 | 标签样式 |
|--------|----------|----------|----------|
| pending | 待处理 | 橙色 | 橙色边框 |
| processing | 处理中 | 蓝色 | 蓝色边框 |
| completed | 已完成 | 绿色 | 绿色边框 |
| cancelled | 已取消 | 灰色 | 灰色边框 |
## 使用示例
### 基础用法
```vue
<template>
<OrderCard
:orderData="orderInfo"
@click="handleCardClick"
@call="handleCall"
@complete="handleComplete"
/>
</template>
<script setup>
import OrderCard from '@/components/OrderCard/index.vue'
const orderInfo = {
id: '001',
title: '空调不制冷,需要维修',
createTime: '2024-01-15 14:30',
contactName: '张先生',
contactPhone: '138****8888',
status: 'pending'
}
const handleCardClick = (orderData) => {
console.log('卡片点击', orderData)
}
const handleCall = (orderData) => {
console.log('呼叫', orderData.contactPhone)
}
const handleComplete = (orderData) => {
console.log('标记完成', orderData.id)
}
</script>
```
### 隐藏操作按钮
```vue
<template>
<OrderCard
:orderData="completedOrder"
:showActions="false"
@click="handleCardClick"
/>
</template>
```
### 自定义操作按钮
```vue
<template>
<OrderCard
:orderData="orderInfo"
@click="handleCardClick"
>
<template #actions>
<button class="custom-btn" @click.stop="handleEdit">
编辑
</button>
<button class="custom-btn" @click.stop="handleDelete">
删除
</button>
</template>
</OrderCard>
</template>
```
## 样式定制
组件支持通过 CSS 变量进行样式定制:
```css
.order-card {
--card-bg: #ffffff;
--card-radius: 12px;
--card-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
--primary-color: #007AFF;
--success-color: #52C41A;
--warning-color: #FF8C00;
--danger-color: #FF3B30;
}
```
## 响应式支持
- 在小屏设备≤375px上自动调整字体大小和间距
- 支持暗色模式自动适配
- 触摸设备优化的交互体验
## 注意事项
1. **数据格式**:确保传入的 `orderData` 包含所有必需字段
2. **状态值**`status` 字段必须是预定义的状态值之一
3. **事件处理**:使用 `@click.stop` 防止操作按钮事件冒泡
4. **性能优化**:大量卡片时建议使用虚拟滚动
## 更新日志
### v1.0.0 (2024-01-15)
- 初始版本发布
- 支持基础工单信息展示
- 支持多种状态和操作按钮
- 支持自定义插槽
- 响应式设计和暗色模式支持

View File

@@ -0,0 +1,91 @@
.order-card {
background-color: #fff;
border-radius: 6px 6px 12px 12px;
box-shadow: 0px 3px 8px 0 rgba(0, 0, 0, 0.12);
margin: 12px;
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
&.expired {
filter: grayscale(100%);
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 14px 12px 16px;
}
.status-info {
display: flex;
align-items: center;
flex: 1;
}
.status-icon {
width: 20px;
height: 20px;
margin-right: 8px;
flex-shrink: 0;
}
.order-title {
font-size: 14px;
font-weight: 500;
color: #333333;
line-height: 1.4;
}
.arrow-icon {
width: 24px;
height: 24px;
}
.status-tag {
box-sizing: border-box;
padding: 6px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
&.tag-0 {
color: #00a6ff;
border: 1px solid #00a6ff;
}
&.tag-1 {
color: #f00044;
border: 1px solid #f00044;
}
&.tag-2 {
color: #40ae36;
border: 1px solid #40ae36;
}
&.tag-3 {
color: #808389;
border: 1px solid #808389;
}
&.tag-4 {
color: #0256ff;
border: 1px solid #0256ff;
}
&.tag-5 {
color: #808389;
border: 1px solid #808389;
}
&.tag-6 {
color: #fd8702;
border: 1px solid #fd8702;
}
}