floorSelector.vue模块设计

This commit is contained in:
minimaxagent1 2025-08-08 15:34:01 +08:00
parent 9bfa797f44
commit 56cd2882d9
4 changed files with 583 additions and 4 deletions

View File

@ -74,3 +74,13 @@ export function getMemorialDetail(id) {
showLoading: false
})
}
/**
* 获取楼层树形结构
* @returns {Promise} 返回楼层树形数据
*/
export function getMemorialTree() {
return get('/app/memorial/listTree', {}, {
showLoading: false
})
}

View File

@ -0,0 +1,145 @@
# 楼层选择组件 (FloorSelector)
## 功能描述
楼层选择组件是一个三层级联选择器,用于选择楼层、区域和单元。组件会根据接口返回的树形数据自动渲染选择项。
## 接口数据格式
组件期望的接口返回格式:
```json
{
"msg": "操作成功",
"code": 200,
"data": [
{
"id": "12",
"label": "寒山寺",
"type": 1,
"children": [
{
"id": "1",
"label": "1F",
"type": 2,
"children": [
{
"id": "23",
"label": "A区",
"type": 3,
"children": [
{
"id": "16",
"label": "A01",
"type": 4,
"children": null
}
]
}
]
}
]
}
]
}
```
## 组件属性 (Props)
| 属性名 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| defaultFloorId | String | '' | 默认选中的楼层ID |
| defaultAreaId | String | '' | 默认选中的区域ID |
| defaultUnitId | String | '' | 默认选中的单元ID |
## 事件 (Events)
| 事件名 | 参数 | 说明 |
|--------|------|------|
| selection-change | { floor, area, unit } | 选择发生变化时触发 |
## 方法 (Methods)
| 方法名 | 参数 | 返回值 | 说明 |
|--------|------|--------|------|
| getCurrentSelection | - | Object | 获取当前选中信息 |
| resetSelection | - | - | 重置选择到默认状态 |
## 使用示例
### 基础使用
```vue
<template>
<FloorSelector @selection-change="handleSelectionChange" />
</template>
<script>
import FloorSelector from './compositons/floorSelector.vue'
export default {
components: {
FloorSelector
},
methods: {
handleSelectionChange(selection) {
console.log('选择变化:', selection)
// selection 包含 { floor, area, unit }
}
}
}
</script>
```
### 设置默认选中项
```vue
<template>
<FloorSelector
:default-floor-id="'1'"
:default-area-id="'23'"
:default-unit-id="'16'"
@selection-change="handleSelectionChange"
/>
</template>
```
### 获取当前选择
```vue
<template>
<FloorSelector ref="floorSelector" />
<button @click="getSelection">获取选择</button>
</template>
<script>
export default {
methods: {
getSelection() {
const selection = this.$refs.floorSelector.getCurrentSelection()
console.log('当前选择:', selection)
}
}
}
</script>
```
## 样式定制
组件使用了以下主要颜色:
- 背景色: `#FFFBF5`
- 边框色: `#C7A26D`
- 未选中按钮: `#F5E6D3`
- 选中按钮: `#8B4513`
- 文字颜色: `#695347`
可以通过修改组件的样式来调整外观。
## 注意事项
1. 组件会自动调用 `/app/memorial/listTree` 接口获取数据
2. 选择楼层时会自动选中第一个区域
3. 选择区域时会自动选中第一个单元
4. 如果接口返回空数据,会显示空状态
5. 网络异常时会显示错误提示

View File

@ -0,0 +1,381 @@
<template>
<view class="floor-selector">
<!-- 楼层选择 -->
<view class="floor-section">
<text class="section-label">楼层:</text>
<view class="floor-buttons">
<view
v-for="floor in floors"
:key="floor.id"
class="floor-btn"
:class="{ active: selectedFloor && selectedFloor.id === floor.id }"
@click="selectFloor(floor)"
>
{{ floor.label }}
</view>
</view>
</view>
<!-- 分隔线 -->
<view class="divider"></view>
<view class="content-section">
<!-- 区域选择 -->
<view class="area-section">
<view
v-for="area in areas"
:key="area.id"
class="area-btn"
:class="{ active: selectedArea && selectedArea.id === area.id }"
@click="selectArea(area)"
>
{{ area.label }}
</view>
</view>
<!-- 分隔线 -->
<view class="vertical-divider"></view>
<!-- 单元选择 -->
<view class="unit-section">
<view class="unit-grid">
<view
v-for="unit in units"
:key="unit.id"
class="unit-btn"
:class="{ active: selectedUnit && selectedUnit.id === unit.id }"
@click="selectUnit(unit)"
>
{{ unit.label }}
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { getMemorialTree } from '@/api/memorial/index.js'
export default {
name: 'FloorSelector',
props: {
// ID
defaultFloorId: {
type: String,
default: ''
},
// ID
defaultAreaId: {
type: String,
default: ''
},
// ID
defaultUnitId: {
type: String,
default: ''
}
},
data() {
return {
treeData: [],
selectedFloor: null,
selectedArea: null,
selectedUnit: null,
loading: false
}
},
computed: {
//
floors() {
if (!this.treeData.length) return []
const memorial = this.treeData[0]
return memorial && memorial.children ? memorial.children : []
},
//
areas() {
if (!this.selectedFloor) return []
return this.selectedFloor.children ? this.selectedFloor.children : []
},
//
units() {
if (!this.selectedArea) return []
return this.selectedArea.children ? this.selectedArea.children : []
}
},
watch: {
//
selectedUnit: {
handler(newUnit) {
this.$emit('selection-change', {
floor: this.selectedFloor,
area: this.selectedArea,
unit: newUnit
})
},
deep: true
}
},
mounted() {
this.loadTreeData()
},
methods: {
//
async loadTreeData() {
this.loading = true
try {
const response = await getMemorialTree()
console.log('楼层树形数据:', response)
if (response && response.code === 200) {
this.treeData = response.data || []
//
this.setDefaultSelection()
} else {
console.error('获取楼层数据失败:', response)
uni.showToast({
title: '获取楼层数据失败',
icon: 'none'
})
}
} catch (error) {
console.error('加载楼层数据失败:', error)
uni.showToast({
title: '网络异常,请稍后重试',
icon: 'none'
})
} finally {
this.loading = false
}
},
//
setDefaultSelection() {
//
if (this.defaultFloorId) {
const floor = this.floors.find(f => f.id === this.defaultFloorId)
if (floor) {
this.selectFloor(floor)
}
} else if (this.floors.length > 0) {
//
this.selectFloor(this.floors[0])
}
//
if (this.defaultAreaId && this.selectedFloor) {
const area = this.areas.find(a => a.id === this.defaultAreaId)
if (area) {
this.selectArea(area)
}
} else if (this.areas.length > 0) {
//
this.selectArea(this.areas[0])
}
//
if (this.defaultUnitId && this.selectedArea) {
const unit = this.units.find(u => u.id === this.defaultUnitId)
if (unit) {
this.selectUnit(unit)
}
} else if (this.units.length > 0) {
//
this.selectUnit(this.units[0])
}
},
//
selectFloor(floor) {
this.selectedFloor = floor
this.selectedArea = null
this.selectedUnit = null
//
if (this.areas.length > 0) {
this.selectArea(this.areas[0])
}
},
//
selectArea(area) {
this.selectedArea = area
this.selectedUnit = null
//
if (this.units.length > 0) {
this.selectUnit(this.units[0])
}
},
//
selectUnit(unit) {
this.selectedUnit = unit
},
//
getCurrentSelection() {
return {
floor: this.selectedFloor,
area: this.selectedArea,
unit: this.selectedUnit
}
},
//
resetSelection() {
this.selectedFloor = null
this.selectedArea = null
this.selectedUnit = null
this.setDefaultSelection()
}
}
}
</script>
<style lang="scss" scoped>
.floor-selector {
padding: 42rpx 44rpx 30rpx 44rpx;
width: 750rpx;
background-color: #FFFBF5;
border-radius: 20rpx;
border: 1rpx solid #C7A26D;
box-sizing: border-box;
}
.floor-section {
display: flex;
align-items: center;
margin-bottom: 26rpx;
border:1rpx solid #C7A26D;
}
.section-label {
margin-right: 10rpx;
width: 96rpx;
height: 44rpx;
font-weight: 400;
font-size: 32rpx;
color: #3D3D3D;
line-height: 44rpx;
text-align: left;
}
.floor-buttons {
display: flex;
gap: 20rpx;
}
.floor-btn {
padding: 4rpx 49rpx;
color: #A24242;
border-radius: 8rpx;
font-size: 28rpx;
cursor: pointer;
transition: all 0.3s ease;
width: 134rpx;
height: 52rpx;
background: #FFF1DD;
border: 1rpx solid #A24242;
&.active {
background-color: #A24242;
color: #FFF1DD;
}
}
.divider {
height: 1rpx;
background: repeating-linear-gradient(
to right,
#A24242 0,
#A24242 8rpx,
transparent 8rpx,
transparent 16rpx
);
margin: 26rpx 0 30rpx 0;
}
.content-section {
display: flex;
height: calc(100% - 120rpx);
}
.area-section {
width: 180rpx;
display: flex;
flex-direction: column;
gap: 16rpx;
border: 1rpx solid #A24242;
}
.area-btn {
padding: 4rpx 40rpx;
color: #A24242;
border-radius: 8rpx;
font-size: 28rpx;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
box-sizing: border-box;
width: 134rpx;
height: 52rpx;
background: #FFF1DD;
border: 1rpx solid #A24242;
&.active {
background-color: #A24242;
color: #FFF1DD;
}
}
.vertical-divider {
width: 1rpx;
background: repeating-linear-gradient(
to bottom,
#C7A26D 0,
#C7A26D 4rpx,
transparent 4rpx,
transparent 8rpx
);
margin: 0 48rpx 0 0;
}
.unit-section {
border: 1rpx solid #f47fef;
flex: 1;
margin-top: 34rpx;
}
.unit-grid {
border: 1rpx solid #f47fef;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
}
.unit-btn {
padding: 16rpx 12rpx;
color: #A24242;
border-radius: 8rpx;
font-size: 28rpx;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
width: 134rpx;
height: 52rpx;
background: #FFF1DD;
border: 1rpx solid #A24242;
&.active {
background-color: #A24242;
color: #FFF1DD;
border-color: #A24242;
}
}
</style>

View File

@ -29,6 +29,16 @@
@item-click="handleItemClick"
ref="enshrinedList"
/>
<!-- 楼层选择器 -->
<view class="floor-selector-container">
<FloorSelector
ref="floorSelector"
:default-floor-id="defaultFloorId"
:default-area-id="defaultAreaId"
:default-unit-id="defaultUnitId"
@selection-change="handleSelectionChange"
/>
</view>
</view>
<bottom-button
title="供奉"
@ -43,6 +53,7 @@ import {CommonEnum} from '@/enum/common.js'
import SearchBox from "../../components/search-box/search-box.vue"
import StatusDisplay from "../../components/status-display/status-display.vue"
import EnshrinedList from "./compositons/enshrinedList.vue"
import FloorSelector from "./compositons/floorSelector.vue"
import BottomButton from "../../components/bottom-button/bottom-button.vue";
export default {
@ -50,14 +61,25 @@ export default {
BottomButton,
SearchBox,
StatusDisplay,
EnshrinedList
EnshrinedList,
FloorSelector
},
data() {
return {
CommonEnum,
searchName: '',
loading: false,
memorialId: '16' // 殿ID
memorialId: '16', // 殿ID
//
defaultFloorId: '',
defaultAreaId: '',
defaultUnitId: '',
//
currentSelection: {
floor: null,
area: null,
unit: null
}
}
},
onLoad(options) {
@ -100,7 +122,22 @@ export default {
title: `查看 ${item.worshiperName} 的供奉记录`,
icon: 'none'
})
}
},
//
handleSelectionChange(selection) {
console.log('楼层选择变化:', selection)
this.currentSelection = selection
//
// ID
if (selection.unit) {
console.log('选中单元:', selection.unit.label, 'ID:', selection.unit.id)
//
// this.queryEnshrinedByUnit(selection.unit.id)
}
},
}
}
</script>
@ -117,7 +154,13 @@ export default {
display: flex;
align-items: center;
flex-direction: column;
padding: 0 15rpx 40rpx 15rpx;
padding-bottom: 40rpx;
box-sizing: border-box;
}
.floor-selector-container {
margin: 30rpx 0;
display: flex;
justify-content: center;
}
</style>