获取设备和套餐

This commit is contained in:
WindowBird 2025-08-19 10:11:19 +08:00
parent 879190c956
commit 77e116b7fa
6 changed files with 397 additions and 47 deletions

116
api/README.md Normal file
View File

@ -0,0 +1,116 @@
# API 模块使用说明
## 概述
本项目使用统一的API请求工具 `@/utils/request.js`所有API请求都通过该工具进行无需重复配置基地址和请求头。
## 目录结构
```
api/
├── index.js # API模块统一导出
├── lease/ # 租赁相关API
│ └── lease.js
├── device/ # 设备相关API
│ └── device.js
├── banner/ # 轮播图相关API
│ └── banner.js
├── article/ # 文章相关API
│ └── article.js
└── auth/ # 认证相关API
└── auth.js
```
## 使用方法
### 1. 导入API方法
```javascript
// 方式1从具体模块导入
import { getDeviceTypes, getPeriodPackages } from '@/api/lease/lease.js'
// 方式2从统一入口导入
import { getDeviceTypes, getPeriodPackages } from '@/api/index.js'
```
### 2. 在组件中使用
```javascript
export default {
methods: {
async fetchData() {
try {
const response = await getDeviceTypes()
if (response.code === 200) {
this.deviceTypes = response.data
} else {
throw new Error(response.message || '请求失败')
}
} catch (error) {
console.error('获取数据失败:', error)
uni.showToast({
title: error.message || '获取数据失败',
icon: 'error',
})
}
}
}
}
```
## API方法命名规范
- 获取列表:`getXxxList`
- 获取详情:`getXxxDetail`
- 创建:`createXxx`
- 更新:`updateXxx`
- 删除:`deleteXxx`
## 请求工具特性
### 自动处理
- ✅ 基地址配置
- ✅ 请求头设置
- ✅ Token管理
- ✅ Loading状态
- ✅ 错误处理
- ✅ 超时处理
### 配置选项
```javascript
request({
url: '/api/endpoint',
method: 'GET',
params: { id: 1 },
loadingText: '加载中...', // 自定义loading文本
showLoading: true, // 是否显示loading
timeout: 60000, // 超时时间
noToken: false, // 是否需要token
})
```
## 环境配置
请求工具会根据当前环境自动选择对应的配置:
- **开发环境**`http://192.168.2.114:4601`
- **体验版**`http://192.168.2.114:4601`
- **正式版**`http://192.168.2.114:4601`
## 错误处理
所有API请求都会自动处理以下错误
- 401登录过期自动跳转登录页
- 403权限不足
- 404资源不存在
- 500服务器错误
- 网络错误:超时、连接失败等
## 注意事项
1. **方法名冲突**避免页面方法名与API方法名相同建议使用别名导入
2. **Loading管理**请求工具已内置loading管理无需手动处理
3. **错误提示**:统一使用 `uni.showToast` 显示错误信息
4. **Token处理**开发环境支持临时token生产环境使用真实token

6
api/index.js Normal file
View File

@ -0,0 +1,6 @@
// API模块统一导出
export * from './lease/lease.js'
export * from './device/device.js'
export * from './banner/banner.js'
export * from './article/article.js'
export * from './auth/auth.js'

29
api/lease/lease.js Normal file
View File

@ -0,0 +1,29 @@
import request from '@/utils/request'
/**
* 获取设备类型列表
* @returns {Promise} 返回设备类型列表数据
*/
export function getDeviceTypes() {
return request({
url: '/app/type/list',
method: 'GET',
loadingText: '加载设备类型中...',
})
}
/**
* 根据设备类型获取租赁套餐列表
* @param {string} typeId - 设备类型ID
* @returns {Promise} 返回租赁套餐列表数据
*/
export function getPeriodPackages(typeId) {
return request({
url: '/app/suit/list',
method: 'GET',
params: {
typeId: typeId
},
loadingText: '加载套餐中...',
})
}

View File

@ -31,7 +31,7 @@
/> />
<!-- 底部导航已由系统tabBar处理 --> <!-- 底部导航已由系统tabBar处理 -->
</view> </view>
</template> </template>
<script> <script>
@ -43,14 +43,14 @@ import { getNewAnnouncement } from '../../api/article/article.js'
import { getBannerList } from '../../api/banner/banner.js' import { getBannerList } from '../../api/banner/banner.js'
import { getDeviceList } from '../../api/device/device.js' import { getDeviceList } from '../../api/device/device.js'
export default { export default {
components: { components: {
AnnouncementBar, AnnouncementBar,
BannerSwiper, BannerSwiper,
EquipmentList, EquipmentList,
}, },
data() { data() {
return { return {
// //
indicatorDots: true, indicatorDots: true,
autoplay: true, autoplay: true,
@ -72,11 +72,11 @@ export default {
// //
equipmentList: [], equipmentList: [],
} }
}, },
// //
onLoad() { onLoad() {
this.fetchAnnouncement() this.fetchAnnouncement()
this.fetchBannerList() this.fetchBannerList()
this.fetchDeviceList() this.fetchDeviceList()
@ -270,7 +270,7 @@ export default {
}) })
}, },
}, },
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -280,5 +280,5 @@ export default {
max-width: 750rpx; max-width: 750rpx;
margin: 0 auto; margin: 0 auto;
z-index: -2; z-index: -2;
} }
</style> </style>

View File

@ -58,7 +58,9 @@
<view class="form-item"> <view class="form-item">
<text class="field-label">租赁设备</text> <text class="field-label">租赁设备</text>
<view class="selector" @click="selectEquipment"> <view class="selector" @click="selectEquipment">
<text class="selector-text">{{ formData.equipment || '选择设备类型' }}</text> <text :class="['selector-text', { placeholder: !formData.equipment }]">
{{ formData.equipment || '选择设备类型' }}
</text>
<text class="arrow-icon">></text> <text class="arrow-icon">></text>
</view> </view>
</view> </view>
@ -67,7 +69,9 @@
<view class="form-item"> <view class="form-item">
<text class="field-label">租赁周期</text> <text class="field-label">租赁周期</text>
<view class="selector" @click="selectPeriod"> <view class="selector" @click="selectPeriod">
<text class="selector-text">{{ formData.period || '1年' }}</text> <text :class="['selector-text', { placeholder: !formData.period }]">
{{ formData.period || '请先选择设备类型' }}
</text>
<text class="arrow-icon">></text> <text class="arrow-icon">></text>
</view> </view>
</view> </view>
@ -76,7 +80,13 @@
<!-- 支付区域 --> <!-- 支付区域 -->
<view class="payment-section"> <view class="payment-section">
<button class="pay-button" @click="handlePayment">立即支付 ¥{{ totalAmount }}</button> <button
:class="['pay-button', { disabled: !canPay }]"
@click="handlePayment"
:disabled="!canPay"
>
{{ canPay ? `立即支付 ¥${totalAmount}` : '请完善信息' }}
</button>
<view class="payment-details"> <view class="payment-details">
<view class="details-header" @click="toggleDetails"> <view class="details-header" @click="toggleDetails">
@ -87,6 +97,18 @@
</view> </view>
<view v-if="showDetails" class="details-content"> <view v-if="showDetails" class="details-content">
<view v-if="selectedDevice" class="detail-item">
<text class="detail-label">设备类型</text>
<text class="detail-value">{{ selectedDevice.name }}</text>
</view>
<view v-if="selectedPackage" class="detail-item">
<text class="detail-label">租赁周期</text>
<text class="detail-value">{{ selectedPackage.name }}</text>
</view>
<view v-if="selectedPackage" class="detail-item">
<text class="detail-label">租赁天数</text>
<text class="detail-value">{{ selectedPackage.day }}</text>
</view>
<view class="detail-item"> <view class="detail-item">
<text class="detail-label">租金</text> <text class="detail-label">租金</text>
<text class="detail-value">¥{{ totalAmount }}</text> <text class="detail-value">¥{{ totalAmount }}</text>
@ -103,6 +125,7 @@
<script> <script>
import commonEnum from '../../enum/commonEnum' import commonEnum from '../../enum/commonEnum'
import MapLocation from '@/components/map-location/map-location.vue' import MapLocation from '@/components/map-location/map-location.vue'
import { getDeviceTypes as fetchDeviceTypes, getPeriodPackages as fetchPeriodPackages } from '@/api/lease/lease.js'
export default { export default {
name: 'LeasePage', name: 'LeasePage',
@ -113,10 +136,19 @@ export default {
commonEnum() { commonEnum() {
return commonEnum return commonEnum
}, },
//
canPay() {
return this.formData.name.trim() &&
this.formData.phone.trim() &&
this.formData.address.trim() &&
this.formData.equipmentId &&
this.formData.periodId &&
parseFloat(this.totalAmount) > 0
}
}, },
onLoad() { onLoad() {
// //
//this.getCurrentLocation() this.getDeviceTypes()
}, },
data() { data() {
return { return {
@ -126,10 +158,16 @@ export default {
address: '', address: '',
detailAddress: '', detailAddress: '',
equipment: '', equipment: '',
period: '1年', equipmentId: '', // ID
period: '',
periodId: '', // ID
}, },
showDetails: false, showDetails: false,
totalAmount: '100.10', totalAmount: '0.00',
deviceTypes: [], //
periodPackages: [], //
selectedDevice: null, //
selectedPackage: null, //
} }
}, },
methods: { methods: {
@ -153,23 +191,112 @@ export default {
console.log('地图已打开') console.log('地图已打开')
// //
}, },
//
async getDeviceTypes() {
try {
const response = await fetchDeviceTypes()
if (response.code === 200) {
this.deviceTypes = response.data
console.log('设备类型列表:', this.deviceTypes)
} else {
throw new Error(response.message || '获取设备类型失败')
}
} catch (error) {
console.error('获取设备类型失败:', error)
uni.showToast({
title: error.message || '获取设备类型失败',
icon: 'error',
})
}
},
//
async getPeriodPackages(typeId) {
try {
const response = await fetchPeriodPackages(typeId)
if (response.code === 200) {
this.periodPackages = response.data
console.log('租赁套餐列表:', this.periodPackages)
} else {
throw new Error(response.message || '获取租赁套餐失败')
}
} catch (error) {
console.error('获取租赁套餐失败:', error)
uni.showToast({
title: error.message || '获取租赁套餐失败',
icon: 'error',
})
}
},
selectEquipment() { selectEquipment() {
// //
if (this.deviceTypes.length === 0) {
uni.showToast({
title: '设备类型加载中,请稍后重试',
icon: 'none',
})
return
}
//
const itemList = this.deviceTypes.map(item => item.name)
uni.showActionSheet({ uni.showActionSheet({
itemList: ['节能灶', '燃烧器', '燃气灶', '电磁炉'], itemList: itemList,
success: res => { success: res => {
const equipmentList = ['节能灶', '燃烧器', '燃气灶', '电磁炉'] const selectedDevice = this.deviceTypes[res.tapIndex]
this.formData.equipment = equipmentList[res.tapIndex] this.selectedDevice = selectedDevice
this.formData.equipment = selectedDevice.name
this.formData.equipmentId = selectedDevice.id
//
this.formData.period = ''
this.formData.periodId = ''
this.selectedPackage = null
this.totalAmount = '0.00'
//
this.getPeriodPackages(selectedDevice.id)
console.log('选中设备:', selectedDevice)
}, },
}) })
}, },
selectPeriod() { selectPeriod() {
// //
if (!this.formData.equipmentId) {
uni.showToast({
title: '请先选择设备类型',
icon: 'none',
})
return
}
//
if (this.periodPackages.length === 0) {
uni.showToast({
title: '套餐加载中,请稍后重试',
icon: 'none',
})
return
}
//
const itemList = this.periodPackages.map(item => `${item.name} ¥${item.amount}`)
uni.showActionSheet({ uni.showActionSheet({
itemList: ['1个月', '3个月', '6个月', '1年', '2年'], itemList: itemList,
success: res => { success: res => {
const periodList = ['1个月', '3个月', '6个月', '1年', '2年'] const selectedPackage = this.periodPackages[res.tapIndex]
this.formData.period = periodList[res.tapIndex] this.selectedPackage = selectedPackage
this.formData.period = selectedPackage.name
this.formData.periodId = selectedPackage.id
this.totalAmount = selectedPackage.amount.toFixed(2)
console.log('选中套餐:', selectedPackage)
}, },
}) })
}, },
@ -177,12 +304,69 @@ export default {
this.showDetails = !this.showDetails this.showDetails = !this.showDetails
}, },
handlePayment() { handlePayment() {
//
if (!this.formData.name.trim()) {
uni.showToast({
title: '请输入姓名',
icon: 'none',
})
return
}
if (!this.formData.phone.trim()) {
uni.showToast({
title: '请输入手机号',
icon: 'none',
})
return
}
if (!this.formData.address.trim()) {
uni.showToast({
title: '请选择地址',
icon: 'none',
})
return
}
if (!this.formData.equipmentId) {
uni.showToast({
title: '请选择设备类型',
icon: 'none',
})
return
}
if (!this.formData.periodId) {
uni.showToast({
title: '请选择租赁周期',
icon: 'none',
})
return
}
if (parseFloat(this.totalAmount) <= 0) {
uni.showToast({
title: '金额无效',
icon: 'none',
})
return
}
// //
uni.showModal({ uni.showModal({
title: '确认支付', title: '确认支付',
content: `确认支付 ¥${this.totalAmount} 吗?`, content: `确认支付 ¥${this.totalAmount} 吗?\n\n设备${this.formData.equipment}\n周期${this.formData.period}`,
success: res => { success: res => {
if (res.confirm) { if (res.confirm) {
// API
console.log('支付信息:', {
...this.formData,
amount: this.totalAmount,
selectedDevice: this.selectedDevice,
selectedPackage: this.selectedPackage
})
uni.showToast({ uni.showToast({
title: '支付成功', title: '支付成功',
icon: 'success', icon: 'success',
@ -307,10 +491,8 @@ export default {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
height: 80rpx; height: 80rpx;
border-radius: 12rpx; border-radius: 12rpx;
padding: 0 20rpx; padding: 0 20rpx;
transition: all 0.3s ease; transition: all 0.3s ease;
&:active { &:active {
@ -321,6 +503,10 @@ export default {
.selector-text { .selector-text {
font-size: 28rpx; font-size: 28rpx;
color: #666; color: #666;
&.placeholder {
color: #999;
}
} }
.arrow-icon { .arrow-icon {
@ -350,6 +536,18 @@ export default {
font-weight: bold; font-weight: bold;
margin-bottom: 40rpx; margin-bottom: 40rpx;
box-shadow: 0 10rpx 30rpx rgba(255, 154, 158, 0.3); box-shadow: 0 10rpx 30rpx rgba(255, 154, 158, 0.3);
transition: all 0.3s ease;
&.disabled {
background: #ccc;
color: #999;
box-shadow: none;
}
&:not(.disabled):active {
transform: translateY(2rpx);
box-shadow: 0 8rpx 20rpx rgba(255, 154, 158, 0.4);
}
} }
.payment-details { .payment-details {
@ -425,3 +623,4 @@ export default {
} }
} }
</style> </style>

View File

@ -7,7 +7,7 @@
<!-- 登录按钮 --> <!-- 登录按钮 -->
<button :disabled="loginLoading" class="login-btn" @click="getPhoneNumber"> <button :disabled="loginLoading" class="login-btn" @click="getPhoneNumber">
<text class="btn-text">{{ loginLoading ? '登录中...' : '微信用户一键登录' }}</text> <text class="btn-text">{{ loginLoading ? '登录中...' : '微信用户一键登录' }}</text>
</button> </button>
</view> </view>
<!-- 底部协议 --> <!-- 底部协议 -->
@ -246,38 +246,38 @@ export default {
wxLogin(data) wxLogin(data)
.then(res => { .then(res => {
if (that.pageLoading) { if (that.pageLoading) {
that.pageLoading.hide() that.pageLoading.hide()
} }
forceHideLoading() forceHideLoading()
if (res.code == 200) { if (res.code == 200) {
console.log(res, 'resres') console.log(res, 'resres')
uni.setStorageSync('token', res.token) uni.setStorageSync('token', res.token)
uni.showToast({ uni.showToast({
title: '登录成功', title: '登录成功',
icon: 'success', icon: 'success',
duration: 1500, duration: 1500,
}) })
setTimeout(() => { setTimeout(() => {
that.ceshi() that.ceshi()
}, 1500) }, 1500)
} else { } else {
uni.showToast({ uni.showToast({
title: res.msg || '登录失败', title: res.msg || '登录失败',
icon: 'none', icon: 'none',
}) })
} }
}) })
.catch(error => { .catch(error => {
if (that.pageLoading) { if (that.pageLoading) {
that.pageLoading.hide() that.pageLoading.hide()
} }
forceHideLoading() forceHideLoading()
console.error('登录失败:', error) console.error('登录失败:', error)
uni.showToast({ uni.showToast({
title: '登录失败', title: '登录失败',
icon: 'none', icon: 'none',
}) })
}) })
@ -329,7 +329,7 @@ page {
.logo-image { .logo-image {
width: 276rpx; width: 276rpx;
height: 276rpx; height: 276rpx;
} }
.main-content { .main-content {
padding: 0 53rpx; padding: 0 53rpx;