获取设备和套餐

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

View File

@ -58,7 +58,9 @@
<view class="form-item">
<text class="field-label">租赁设备</text>
<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>
</view>
</view>
@ -67,7 +69,9 @@
<view class="form-item">
<text class="field-label">租赁周期</text>
<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>
</view>
</view>
@ -76,7 +80,13 @@
<!-- 支付区域 -->
<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="details-header" @click="toggleDetails">
@ -87,6 +97,18 @@
</view>
<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">
<text class="detail-label">租金</text>
<text class="detail-value">¥{{ totalAmount }}</text>
@ -103,6 +125,7 @@
<script>
import commonEnum from '../../enum/commonEnum'
import MapLocation from '@/components/map-location/map-location.vue'
import { getDeviceTypes as fetchDeviceTypes, getPeriodPackages as fetchPeriodPackages } from '@/api/lease/lease.js'
export default {
name: 'LeasePage',
@ -113,10 +136,19 @@ export default {
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() {
//
//this.getCurrentLocation()
//
this.getDeviceTypes()
},
data() {
return {
@ -126,10 +158,16 @@ export default {
address: '',
detailAddress: '',
equipment: '',
period: '1年',
equipmentId: '', // ID
period: '',
periodId: '', // ID
},
showDetails: false,
totalAmount: '100.10',
totalAmount: '0.00',
deviceTypes: [], //
periodPackages: [], //
selectedDevice: null, //
selectedPackage: null, //
}
},
methods: {
@ -153,23 +191,112 @@ export default {
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() {
//
//
if (this.deviceTypes.length === 0) {
uni.showToast({
title: '设备类型加载中,请稍后重试',
icon: 'none',
})
return
}
//
const itemList = this.deviceTypes.map(item => item.name)
uni.showActionSheet({
itemList: ['节能灶', '燃烧器', '燃气灶', '电磁炉'],
itemList: itemList,
success: res => {
const equipmentList = ['节能灶', '燃烧器', '燃气灶', '电磁炉']
this.formData.equipment = equipmentList[res.tapIndex]
const selectedDevice = this.deviceTypes[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() {
//
//
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({
itemList: ['1个月', '3个月', '6个月', '1年', '2年'],
itemList: itemList,
success: res => {
const periodList = ['1个月', '3个月', '6个月', '1年', '2年']
this.formData.period = periodList[res.tapIndex]
const selectedPackage = this.periodPackages[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
},
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({
title: '确认支付',
content: `确认支付 ¥${this.totalAmount} 吗?`,
content: `确认支付 ¥${this.totalAmount} 吗?\n\n设备${this.formData.equipment}\n周期${this.formData.period}`,
success: res => {
if (res.confirm) {
// API
console.log('支付信息:', {
...this.formData,
amount: this.totalAmount,
selectedDevice: this.selectedDevice,
selectedPackage: this.selectedPackage
})
uni.showToast({
title: '支付成功',
icon: 'success',
@ -307,10 +491,8 @@ export default {
justify-content: space-between;
align-items: center;
height: 80rpx;
border-radius: 12rpx;
padding: 0 20rpx;
transition: all 0.3s ease;
&:active {
@ -321,6 +503,10 @@ export default {
.selector-text {
font-size: 28rpx;
color: #666;
&.placeholder {
color: #999;
}
}
.arrow-icon {
@ -350,6 +536,18 @@ export default {
font-weight: bold;
margin-bottom: 40rpx;
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 {
@ -425,3 +623,4 @@ export default {
}
}
</style>

View File

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