358 lines
7.7 KiB
Vue
358 lines
7.7 KiB
Vue
<template>
|
||
<view class="content">
|
||
|
||
<view class="header-title">
|
||
{{ $t('appointment.allServices') }}
|
||
</view>
|
||
|
||
<view class="divider"></view>
|
||
|
||
<view class="main-container">
|
||
|
||
<view class="category-sidebar">
|
||
|
||
<view
|
||
class="category-item"
|
||
v-for="(category, index) in categories"
|
||
:key="category.id"
|
||
@click="clickType(index, category.id)"
|
||
:class="{ 'category-item-active': currentIndex === index }">
|
||
<view class="category-indicator" v-if="currentIndex === index"></view>
|
||
<text class="category-text">{{ getCategoryName(category) }}</text>
|
||
</view>
|
||
|
||
</view>
|
||
|
||
<view class="divider-vertical"></view>
|
||
|
||
<view class="service-list">
|
||
|
||
<!-- 加载状态 -->
|
||
<view v-if="loading" class="loading-container">
|
||
<text class="loading-text">{{ $t('common.loading') || '加载中...' }}</text>
|
||
</view>
|
||
|
||
<!-- 服务列表 -->
|
||
<view v-else-if="services.length > 0" class="service-items">
|
||
<view class="service-item" v-for="service in services" :key="service.id" @click="goToServiceDetail(service.id)">
|
||
|
||
<view class="service-image-wrapper">
|
||
<image
|
||
v-if="service.image"
|
||
:src="getImageUrl(service.image)"
|
||
class="service-image"
|
||
mode="aspectFill"
|
||
@error="handleImageError(service)"></image>
|
||
<view v-else class="service-image-placeholder">
|
||
<text class="placeholder-icon">📷</text>
|
||
</view>
|
||
</view>
|
||
|
||
<text class="service-title">{{ getServiceName(service) }}</text>
|
||
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 空状态 -->
|
||
<view v-else class="empty-container">
|
||
<text class="empty-text">{{ $t('common.noData') || '暂无服务' }}</text>
|
||
</view>
|
||
|
||
</view>
|
||
|
||
</view>
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { updateTabBarI18n } from '@/utils/tabbar-i18n.js'
|
||
import { AppServer } from '@/modules/api/AppServer.js'
|
||
import Config from '@/modules/Config.js'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
currentIndex: 0,
|
||
currentCategoryId: null,
|
||
categories: [],
|
||
services: [],
|
||
loading: false,
|
||
currentLanguage: 'zh'
|
||
}
|
||
},
|
||
onLoad() {
|
||
// 获取当前语言
|
||
this.currentLanguage = uni.getStorageSync('language') || 'zh'
|
||
// 加载分类和服务
|
||
this.loadCategories()
|
||
},
|
||
onShow() {
|
||
updateTabBarI18n(this)
|
||
},
|
||
methods: {
|
||
// 加载分类列表
|
||
async loadCategories() {
|
||
try {
|
||
const appserver = new AppServer()
|
||
const response = await appserver.GetCategories({ language: this.currentLanguage })
|
||
|
||
console.log('分类列表响应:', response)
|
||
|
||
if (response.code === 0 && response.data) {
|
||
this.categories = response.data.categories || response.data || []
|
||
|
||
// 默认选中第一个分类
|
||
if (this.categories.length > 0) {
|
||
this.currentIndex = 0
|
||
this.currentCategoryId = this.categories[0].id
|
||
this.loadServices(this.currentCategoryId)
|
||
}
|
||
} else {
|
||
console.error('获取分类失败:', response.message)
|
||
uni.showToast({
|
||
title: response.message || '获取分类失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
} catch (error) {
|
||
console.error('加载分类异常:', error)
|
||
uni.showToast({
|
||
title: '加载分类失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
|
||
// 加载服务列表
|
||
async loadServices(categoryId) {
|
||
this.loading = true
|
||
try {
|
||
const appserver = new AppServer()
|
||
const params = {
|
||
categoryId: categoryId,
|
||
language: this.currentLanguage
|
||
}
|
||
const response = await appserver.GetServices(params)
|
||
|
||
console.log('服务列表响应:', response)
|
||
|
||
if (response.code === 0 && response.data) {
|
||
this.services = response.data.services || response.data || []
|
||
} else {
|
||
console.error('获取服务失败:', response.message)
|
||
this.services = []
|
||
}
|
||
} catch (error) {
|
||
console.error('加载服务异常:', error)
|
||
this.services = []
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
|
||
// 点击分类
|
||
clickType(index, categoryId) {
|
||
this.currentIndex = index
|
||
this.currentCategoryId = categoryId
|
||
this.loadServices(categoryId)
|
||
},
|
||
|
||
// 获取分类名称(根据当前语言)
|
||
getCategoryName(category) {
|
||
if (!category) return ''
|
||
// 后端返回的是单个 name 字段,已经根据语言格式化
|
||
return category.name || ''
|
||
},
|
||
|
||
// 获取服务名称(根据当前语言)
|
||
getServiceName(service) {
|
||
if (!service) return ''
|
||
// 后端返回的是 title 字段,已经根据语言格式化
|
||
return service.title || ''
|
||
},
|
||
|
||
// 获取完整的图片 URL
|
||
getImageUrl(imagePath) {
|
||
if (!imagePath) return ''
|
||
if (imagePath.startsWith('http')) return imagePath
|
||
// 如果是相对路径,拼接 API 基础地址
|
||
const baseUrl = Config.API_BASE_URL || 'http://localhost:3000'
|
||
return `${baseUrl}${imagePath}`
|
||
},
|
||
|
||
// 跳转到服务详情页
|
||
goToServiceDetail(serviceId) {
|
||
uni.navigateTo({
|
||
url: `/pages/service-detail/service-detail?id=${serviceId}`
|
||
})
|
||
},
|
||
|
||
// 处理图片加载错误
|
||
handleImageError(service) {
|
||
console.error('图片加载失败:', service.image)
|
||
// 将图片设置为 null,显示占位符
|
||
service.image = null
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.content {
|
||
display: flex;
|
||
height: 100vh;
|
||
background-color: #F3F4F8;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.header-title {
|
||
width: 100%;
|
||
margin-top: 120rpx;
|
||
font-size: 30rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.divider {
|
||
width: 100%;
|
||
height: 2rpx;
|
||
background-color: #E0E0E0;
|
||
margin-top: 32rpx;
|
||
}
|
||
|
||
.divider-vertical {
|
||
width: 2rpx;
|
||
background-color: #E0E0E0;
|
||
}
|
||
|
||
.main-container {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex: 1;
|
||
height: calc(100vh - 120rpx - 32rpx - 2rpx);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.category-sidebar {
|
||
display: flex;
|
||
flex-direction: column;
|
||
width: 218rpx;
|
||
min-width: 218rpx;
|
||
background-color: #F7F7F7;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.category-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
min-height: 88rpx;
|
||
padding: 16rpx 12rpx;
|
||
position: relative;
|
||
background: #F7F7F7;
|
||
color: #000000;
|
||
transition: all 0.3s ease;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.category-item-active {
|
||
background: linear-gradient(to right, #E5FBFF, #77F6F4);
|
||
color: #00A0BC;
|
||
}
|
||
|
||
.category-indicator {
|
||
width: 6rpx;
|
||
height: 30rpx;
|
||
border-radius: 20rpx;
|
||
background-color: #00A0BC;
|
||
position: absolute;
|
||
left: 12rpx;
|
||
}
|
||
|
||
.category-text {
|
||
font-size: 28rpx;
|
||
text-align: center;
|
||
word-break: break-word;
|
||
line-height: 1.4;
|
||
flex: 1;
|
||
}
|
||
|
||
.service-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding-bottom: 80rpx;
|
||
}
|
||
|
||
.loading-container,
|
||
.empty-container {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 100rpx 0;
|
||
width: 100%;
|
||
}
|
||
|
||
.loading-text,
|
||
.empty-text {
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.service-items {
|
||
display: flex;
|
||
flex-direction: column;
|
||
width: 100%;
|
||
}
|
||
|
||
.service-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
width: 468rpx;
|
||
margin: 40rpx auto 0;
|
||
cursor: pointer;
|
||
transition: transform 0.2s;
|
||
}
|
||
|
||
.service-item:active {
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
.service-image-wrapper {
|
||
width: 100%;
|
||
height: 180rpx;
|
||
border-radius: 10rpx;
|
||
overflow: hidden;
|
||
background-color: #f0f0f0;
|
||
}
|
||
|
||
.service-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.service-image-placeholder {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: linear-gradient(135deg, #e0e0e0 0%, #f5f5f5 100%);
|
||
}
|
||
|
||
.placeholder-icon {
|
||
font-size: 60rpx;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.service-title {
|
||
font-size: 28rpx;
|
||
margin-top: 12rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
</style> |