初始化参股
This commit is contained in:
362
前端/pages/about/index.uvue
Normal file
362
前端/pages/about/index.uvue
Normal file
@@ -0,0 +1,362 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view class="page-scroll" scroll-y>
|
||||
<!-- 公司Logo和名称 -->
|
||||
<view class="header-section">
|
||||
<view class="company-logo">
|
||||
<text class="logo-text">优艺家</text>
|
||||
</view>
|
||||
<text class="company-name">{{ companyInfo.name }}</text>
|
||||
<text class="company-slogan">{{ companyInfo.slogan }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 公司介绍 -->
|
||||
<view class="section">
|
||||
<section-header title="公司简介"></section-header>
|
||||
<view class="intro-card">
|
||||
<text class="intro-text">{{ companyInfo.description }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 我们的优势 -->
|
||||
<view class="section">
|
||||
<section-header title="我们的优势"></section-header>
|
||||
<view class="features-grid">
|
||||
<view class="feature-item" v-for="item in companyInfo.features" :key="item.title">
|
||||
<view class="feature-icon-bg">
|
||||
<text class="feature-icon">✓</text>
|
||||
</view>
|
||||
<text class="feature-title">{{ item.title }}</text>
|
||||
<text class="feature-desc">{{ item.desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 联系方式 -->
|
||||
<view class="section">
|
||||
<section-header title="联系我们"></section-header>
|
||||
<view class="contact-card">
|
||||
<view class="contact-item" @click="callPhone">
|
||||
<view class="contact-icon-bg">
|
||||
<text class="contact-icon">📞</text>
|
||||
</view>
|
||||
<view class="contact-info">
|
||||
<text class="contact-label">客服电话</text>
|
||||
<text class="contact-value">{{ companyInfo.phone }}</text>
|
||||
</view>
|
||||
<text class="contact-arrow">›</text>
|
||||
</view>
|
||||
|
||||
<view class="contact-item" @click="copyWechat">
|
||||
<view class="contact-icon-bg">
|
||||
<text class="contact-icon">💬</text>
|
||||
</view>
|
||||
<view class="contact-info">
|
||||
<text class="contact-label">微信号</text>
|
||||
<text class="contact-value">{{ companyInfo.wechat }}</text>
|
||||
</view>
|
||||
<text class="contact-arrow">›</text>
|
||||
</view>
|
||||
|
||||
<view class="contact-item" @click="openLocation">
|
||||
<view class="contact-icon-bg">
|
||||
<text class="contact-icon">📍</text>
|
||||
</view>
|
||||
<view class="contact-info">
|
||||
<text class="contact-label">公司地址</text>
|
||||
<text class="contact-value">{{ companyInfo.address }}</text>
|
||||
</view>
|
||||
<text class="contact-arrow">›</text>
|
||||
</view>
|
||||
|
||||
<view class="contact-item">
|
||||
<view class="contact-icon-bg">
|
||||
<text class="contact-icon">🕐</text>
|
||||
</view>
|
||||
<view class="contact-info">
|
||||
<text class="contact-label">营业时间</text>
|
||||
<text class="contact-value">{{ companyInfo.workTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部间距 -->
|
||||
<view class="bottom-space"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { getCompanyInfo } from '@/api/index.uts'
|
||||
|
||||
// 特色类型
|
||||
type FeatureItem = {
|
||||
title : string
|
||||
desc : string
|
||||
}
|
||||
|
||||
// 公司信息类型
|
||||
type CompanyInfo = {
|
||||
name : string
|
||||
slogan : string
|
||||
description : string
|
||||
phone : string
|
||||
wechat : string
|
||||
address : string
|
||||
workTime : string
|
||||
features : FeatureItem[]
|
||||
}
|
||||
|
||||
// 公司信息
|
||||
const companyInfo = ref<CompanyInfo>({
|
||||
name: '优艺家沙发翻新',
|
||||
slogan: '让旧沙发焕发新生',
|
||||
description: '',
|
||||
phone: '400-888-8888',
|
||||
wechat: 'youyijia2026',
|
||||
address: '',
|
||||
workTime: '',
|
||||
features: []
|
||||
})
|
||||
|
||||
// 获取公司信息
|
||||
const fetchCompanyInfo = async () => {
|
||||
try {
|
||||
const res = await getCompanyInfo()
|
||||
const data = res.data as UTSJSONObject
|
||||
|
||||
const featuresData = data['features'] as UTSJSONObject[]
|
||||
const features = featuresData.map((item) : FeatureItem => {
|
||||
return {
|
||||
title: item['title'] as string,
|
||||
desc: item['desc'] as string
|
||||
} as FeatureItem
|
||||
})
|
||||
|
||||
companyInfo.value = {
|
||||
name: data['name'] as string,
|
||||
slogan: data['slogan'] as string,
|
||||
description: data['description'] as string,
|
||||
phone: data['phone'] as string,
|
||||
wechat: data['wechat'] as string,
|
||||
address: data['address'] as string,
|
||||
workTime: data['workTime'] as string,
|
||||
features: features
|
||||
} as CompanyInfo
|
||||
} catch (e) {
|
||||
console.error('获取公司信息失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 拨打电话
|
||||
const callPhone = () => {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: companyInfo.value.phone,
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '拨打电话失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 复制微信号
|
||||
const copyWechat = () => {
|
||||
uni.setClipboardData({
|
||||
data: companyInfo.value.wechat,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: '微信号已复制',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 打开位置
|
||||
const openLocation = () => {
|
||||
uni.openLocation({
|
||||
latitude: 31.2,
|
||||
longitude: 121.5,
|
||||
name: companyInfo.value.name,
|
||||
address: companyInfo.value.address,
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '无法打开地图',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
fetchCompanyInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-scroll {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 头部区域 */
|
||||
.header-section {
|
||||
background: linear-gradient(135deg, #D4A574 0%, #B8895A 100%);
|
||||
padding: 60rpx 32rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 50%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #D4A574;
|
||||
}
|
||||
|
||||
.company-name {
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.company-slogan {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* 通用section */
|
||||
.section {
|
||||
padding: 0 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
/* 公司介绍 */
|
||||
.intro-card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
line-height: 48rpx;
|
||||
}
|
||||
|
||||
/* 优势 */
|
||||
.features-grid {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
width: 50%;
|
||||
padding: 24rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.feature-icon-bg {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background-color: #D4A574;
|
||||
border-radius: 50%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 40rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 联系方式 */
|
||||
.contact-card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.contact-item {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 32rpx;
|
||||
border-bottom-width: 1rpx;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #EBEEF5;
|
||||
}
|
||||
|
||||
.contact-item:last-child {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
.contact-icon-bg {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
background-color: #FDF6EE;
|
||||
border-radius: 50%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.contact-icon {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.contact-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.contact-label {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.contact-value {
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.contact-arrow {
|
||||
font-size: 36rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 底部间距 */
|
||||
.bottom-space {
|
||||
height: 60rpx;
|
||||
}
|
||||
</style>
|
||||
489
前端/pages/booking/index.uvue
Normal file
489
前端/pages/booking/index.uvue
Normal file
@@ -0,0 +1,489 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view class="page-scroll" scroll-y>
|
||||
<!-- 表单头部 -->
|
||||
<view class="header-section">
|
||||
<text class="header-title">预约翻新服务</text>
|
||||
<text class="header-desc">填写以下信息,我们会尽快与您联系</text>
|
||||
</view>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<view class="form-section">
|
||||
<!-- 姓名 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">
|
||||
<text class="required">*</text>
|
||||
您的姓名
|
||||
</text>
|
||||
<input
|
||||
class="form-input"
|
||||
type="text"
|
||||
v-model="formData.userName"
|
||||
placeholder="请输入您的姓名"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 电话 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">
|
||||
<text class="required">*</text>
|
||||
联系电话
|
||||
</text>
|
||||
<input
|
||||
class="form-input"
|
||||
type="number"
|
||||
v-model="formData.phone"
|
||||
placeholder="请输入您的手机号"
|
||||
placeholder-class="placeholder"
|
||||
maxlength="11"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 地址 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">
|
||||
<text class="required">*</text>
|
||||
您的地址
|
||||
</text>
|
||||
<input
|
||||
class="form-input"
|
||||
type="text"
|
||||
v-model="formData.address"
|
||||
placeholder="请输入详细地址"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 沙发类型 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">沙发类型</text>
|
||||
<view class="type-grid">
|
||||
<view
|
||||
class="type-item"
|
||||
:class="{ 'type-active': formData.sofaType == item.id }"
|
||||
v-for="item in sofaTypes"
|
||||
:key="item.id"
|
||||
@click="selectSofaType(item.id)"
|
||||
>
|
||||
<text
|
||||
class="type-text"
|
||||
:class="{ 'type-text-active': formData.sofaType == item.id }"
|
||||
>{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 问题描述 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">问题描述</text>
|
||||
<textarea
|
||||
class="form-textarea"
|
||||
v-model="formData.problem"
|
||||
placeholder="请描述沙发的问题(如:皮面开裂、褪色、塌陷等)"
|
||||
placeholder-class="placeholder"
|
||||
maxlength="500"
|
||||
></textarea>
|
||||
<text class="textarea-count">{{ formData.problem.length }}/500</text>
|
||||
</view>
|
||||
|
||||
<!-- 上传图片 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">上传图片(可选)</text>
|
||||
<view class="upload-grid">
|
||||
<view
|
||||
class="upload-item"
|
||||
v-for="(item, index) in imageList"
|
||||
:key="index"
|
||||
>
|
||||
<image class="upload-image" :src="item" mode="aspectFill"></image>
|
||||
<view class="upload-delete" @click="deleteImage(index)">
|
||||
<text class="delete-icon">×</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="upload-add" v-if="imageList.length < 9" @click="chooseImage">
|
||||
<text class="add-icon">+</text>
|
||||
<text class="add-text">添加图片</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="upload-tip">最多可上传9张图片</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部间距 -->
|
||||
<view class="bottom-space"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view class="submit-bar">
|
||||
<view class="submit-btn" @click="handleSubmit">
|
||||
<text class="submit-text">提交预约</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { submitBooking } from '@/api/index.uts'
|
||||
import { SOFA_CATEGORIES } from '@/utils/config.uts'
|
||||
|
||||
// 表单类型
|
||||
type FormData = {
|
||||
userName : string
|
||||
phone : string
|
||||
address : string
|
||||
sofaType : string
|
||||
problem : string
|
||||
}
|
||||
|
||||
// 沙发类型
|
||||
type SofaType = {
|
||||
id : string
|
||||
name : string
|
||||
}
|
||||
|
||||
// 表单数据
|
||||
const formData = ref<FormData>({
|
||||
userName: '',
|
||||
phone: '',
|
||||
address: '',
|
||||
sofaType: '',
|
||||
problem: ''
|
||||
})
|
||||
|
||||
// 图片列表
|
||||
const imageList = ref<string[]>([])
|
||||
|
||||
// 沙发类型列表
|
||||
const sofaTypes = ref<SofaType[]>([])
|
||||
|
||||
// 提交中
|
||||
const submitting = ref(false)
|
||||
|
||||
// 初始化沙发类型
|
||||
const initSofaTypes = () => {
|
||||
sofaTypes.value = SOFA_CATEGORIES.filter((item) : boolean => item.id != 'all').map((item) : SofaType => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name
|
||||
} as SofaType
|
||||
})
|
||||
}
|
||||
|
||||
// 选择沙发类型
|
||||
const selectSofaType = (id : string) => {
|
||||
formData.value.sofaType = id
|
||||
}
|
||||
|
||||
// 选择图片
|
||||
const chooseImage = () => {
|
||||
uni.chooseImage({
|
||||
count: 9 - imageList.value.length,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
imageList.value = [...imageList.value, ...res.tempFilePaths]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除图片
|
||||
const deleteImage = (index : number) => {
|
||||
imageList.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 验证表单
|
||||
const validateForm = () : boolean => {
|
||||
if (formData.value.userName.trim() == '') {
|
||||
uni.showToast({
|
||||
title: '请输入您的姓名',
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
const phone = formData.value.phone.trim()
|
||||
if (phone == '') {
|
||||
uni.showToast({
|
||||
title: '请输入联系电话',
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
// 简单验证手机号
|
||||
if (phone.length != 11) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的手机号',
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
if (formData.value.address.trim() == '') {
|
||||
uni.showToast({
|
||||
title: '请输入您的地址',
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 提交预约
|
||||
const handleSubmit = async () => {
|
||||
if (!validateForm()) return
|
||||
if (submitting.value) return
|
||||
|
||||
submitting.value = true
|
||||
|
||||
try {
|
||||
const data = {
|
||||
userName: formData.value.userName,
|
||||
phone: formData.value.phone,
|
||||
address: formData.value.address,
|
||||
sofaType: formData.value.sofaType,
|
||||
problem: formData.value.problem,
|
||||
images: imageList.value
|
||||
} as UTSJSONObject
|
||||
|
||||
const res = await submitBooking(data)
|
||||
const result = res.data as UTSJSONObject
|
||||
|
||||
uni.showModal({
|
||||
title: '预约成功',
|
||||
content: result['message'] as string,
|
||||
showCancel: false,
|
||||
success: () => {
|
||||
// 返回上一页或首页
|
||||
uni.navigateBack({
|
||||
fail: () => {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('提交预约失败', e)
|
||||
uni.showToast({
|
||||
title: '提交失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
submitting.value = false
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
initSofaTypes()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-scroll {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.header-section {
|
||||
background: linear-gradient(135deg, #D4A574 0%, #B8895A 100%);
|
||||
padding: 48rpx 32rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* 表单区域 */
|
||||
.form-section {
|
||||
background-color: #ffffff;
|
||||
margin: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #F56C6C;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
font-size: 28rpx;
|
||||
height:40px;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #C0C4CC;
|
||||
}
|
||||
|
||||
/* 沙发类型选择 */
|
||||
.type-grid {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.type-item {
|
||||
padding: 16rpx 32rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.type-active {
|
||||
background-color: #D4A574;
|
||||
}
|
||||
|
||||
.type-text {
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.type-text-active {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 文本域 */
|
||||
.form-textarea {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
font-size: 28rpx;
|
||||
height: 200rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.textarea-count {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
text-align: right;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
/* 图片上传 */
|
||||
.upload-grid {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.upload-item {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-right: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.upload-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.upload-delete {
|
||||
position: absolute;
|
||||
top: -16rpx;
|
||||
right: -16rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background-color: #F56C6C;
|
||||
border-radius: 50%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
font-size: 28rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.upload-add {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 12rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-width: 2rpx;
|
||||
border-style: dashed;
|
||||
border-color: #DCDFE6;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
font-size: 48rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.add-text {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
/* 底部间距 */
|
||||
.bottom-space {
|
||||
height: 160rpx;
|
||||
}
|
||||
|
||||
/* 提交按钮 */
|
||||
.submit-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #ffffff;
|
||||
padding: 24rpx 32rpx;
|
||||
padding-bottom: 48rpx;
|
||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
background-color: #D4A574;
|
||||
border-radius: 999rpx;
|
||||
padding: 28rpx 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.submit-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
438
前端/pages/cases/detail.uvue
Normal file
438
前端/pages/cases/detail.uvue
Normal file
@@ -0,0 +1,438 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view class="page-scroll" scroll-y>
|
||||
<!-- 图片画廊 -->
|
||||
<view class="gallery-section">
|
||||
<swiper
|
||||
class="gallery-swiper"
|
||||
circular
|
||||
indicator-dots
|
||||
indicator-color="rgba(255,255,255,0.5)"
|
||||
indicator-active-color="#ffffff"
|
||||
>
|
||||
<swiper-item v-for="(item, index) in caseDetail.afterImages" :key="index">
|
||||
<image
|
||||
class="gallery-image"
|
||||
:src="item"
|
||||
mode="aspectFill"
|
||||
@click="previewImages(index)"
|
||||
></image>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="gallery-tag">
|
||||
<text class="gallery-tag-text">{{ caseDetail.categoryName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<view class="info-section">
|
||||
<text class="case-title">{{ caseDetail.title }}</text>
|
||||
<view class="case-meta">
|
||||
<text class="case-price">{{ caseDetail.price }}</text>
|
||||
<view class="case-stats">
|
||||
<text class="stat-text">👁 {{ caseDetail.views }}</text>
|
||||
<text class="stat-text">❤ {{ caseDetail.likes }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 翻新前后对比 -->
|
||||
<view class="compare-section">
|
||||
<before-after
|
||||
:beforeImage="caseDetail.beforeImages[0] || ''"
|
||||
:afterImage="caseDetail.afterImages[0] || ''"
|
||||
:showTitle="true"
|
||||
></before-after>
|
||||
</view>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<view class="detail-section">
|
||||
<view class="detail-header">
|
||||
<text class="detail-title">翻新详情</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-list">
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">使用材质</text>
|
||||
<text class="detail-value">{{ caseDetail.material }}</text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">翻新工期</text>
|
||||
<text class="detail-value">{{ caseDetail.duration }}</text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">完成日期</text>
|
||||
<text class="detail-value">{{ caseDetail.createTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="detail-desc">
|
||||
<text class="desc-title">案例描述</text>
|
||||
<text class="desc-content">{{ caseDetail.description }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部间距 -->
|
||||
<view class="bottom-space"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="bottom-bar">
|
||||
<view class="bar-left">
|
||||
<view class="bar-btn" @click="handleFavorite">
|
||||
<text class="bar-icon">{{ isFavorite ? '❤️' : '🤍' }}</text>
|
||||
<text class="bar-label">收藏</text>
|
||||
</view>
|
||||
<view class="bar-btn" @click="handleShare">
|
||||
<text class="bar-icon">📤</text>
|
||||
<text class="bar-label">分享</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bar-right">
|
||||
<view class="contact-btn" @click="handleContact">
|
||||
<text class="contact-text">在线咨询</text>
|
||||
</view>
|
||||
<view class="booking-btn" @click="goToBooking">
|
||||
<text class="booking-text">立即预约</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { getCaseDetail } from '@/api/index.uts'
|
||||
|
||||
// 案例详情类型
|
||||
type CaseDetail = {
|
||||
id : string
|
||||
title : string
|
||||
category : string
|
||||
categoryName : string
|
||||
beforeImages : string[]
|
||||
afterImages : string[]
|
||||
description : string
|
||||
material : string
|
||||
duration : string
|
||||
price : string
|
||||
views : number
|
||||
likes : number
|
||||
createTime : string
|
||||
}
|
||||
|
||||
// 案例ID
|
||||
const caseId = ref('')
|
||||
|
||||
// 案例详情
|
||||
const caseDetail = ref<CaseDetail>({
|
||||
id: '',
|
||||
title: '',
|
||||
category: '',
|
||||
categoryName: '',
|
||||
beforeImages: [],
|
||||
afterImages: [],
|
||||
description: '',
|
||||
material: '',
|
||||
duration: '',
|
||||
price: '',
|
||||
views: 0,
|
||||
likes: 0,
|
||||
createTime: ''
|
||||
})
|
||||
|
||||
// 是否收藏
|
||||
const isFavorite = ref(false)
|
||||
|
||||
// 获取案例详情
|
||||
const fetchCaseDetail = async () => {
|
||||
try {
|
||||
const res = await getCaseDetail(caseId.value)
|
||||
const data = res.data as UTSJSONObject
|
||||
|
||||
caseDetail.value = {
|
||||
id: data['id'] as string,
|
||||
title: data['title'] as string,
|
||||
category: data['category'] as string,
|
||||
categoryName: data['categoryName'] as string,
|
||||
beforeImages: data['beforeImages'] as string[],
|
||||
afterImages: data['afterImages'] as string[],
|
||||
description: data['description'] as string,
|
||||
material: data['material'] as string,
|
||||
duration: data['duration'] as string,
|
||||
price: data['price'] as string,
|
||||
views: data['views'] as number,
|
||||
likes: data['likes'] as number,
|
||||
createTime: data['createTime'] as string
|
||||
} as CaseDetail
|
||||
|
||||
// 更新标题
|
||||
uni.setNavigationBarTitle({
|
||||
title: caseDetail.value.title
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('获取案例详情失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 预览图片
|
||||
const previewImages = (index : number) => {
|
||||
uni.previewImage({
|
||||
current: index,
|
||||
urls: caseDetail.value.afterImages
|
||||
})
|
||||
}
|
||||
|
||||
// 收藏
|
||||
const handleFavorite = () => {
|
||||
isFavorite.value = !isFavorite.value
|
||||
uni.showToast({
|
||||
title: isFavorite.value ? '已收藏' : '已取消收藏',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 分享
|
||||
const handleShare = () => {
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序触发分享
|
||||
// #endif
|
||||
uni.showToast({
|
||||
title: '分享功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 在线咨询
|
||||
const handleContact = () => {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: '400-888-8888',
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '拨打电话失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 预约
|
||||
const goToBooking = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/booking/index'
|
||||
})
|
||||
}
|
||||
|
||||
onLoad((options : OnLoadOptions) => {
|
||||
caseId.value = options['id'] ?? ''
|
||||
if (caseId.value != '') {
|
||||
fetchCaseDetail()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-scroll {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 图片画廊 */
|
||||
.gallery-section {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gallery-swiper {
|
||||
height: 500rpx;
|
||||
}
|
||||
|
||||
.gallery-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.gallery-tag {
|
||||
position: absolute;
|
||||
top: 24rpx;
|
||||
left: 24rpx;
|
||||
background-color: rgba(212, 165, 116, 0.9);
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.gallery-tag-text {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 基本信息 */
|
||||
.info-section {
|
||||
background-color: #ffffff;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.case-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.case-meta {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.case-price {
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
color: #D4A574;
|
||||
}
|
||||
|
||||
.case-stats {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.stat-text {
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
/* 对比区域 */
|
||||
.compare-section {
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
/* 详细信息 */
|
||||
.detail-section {
|
||||
background-color: #ffffff;
|
||||
margin: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.detail-list {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 0;
|
||||
border-bottom-width: 1rpx;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #EBEEF5;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 28rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.detail-desc {
|
||||
padding-top: 16rpx;
|
||||
}
|
||||
|
||||
.desc-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.desc-content {
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
line-height: 48rpx;
|
||||
}
|
||||
|
||||
/* 底部间距 */
|
||||
.bottom-space {
|
||||
height: 160rpx;
|
||||
}
|
||||
|
||||
/* 底部操作栏 */
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: #ffffff;
|
||||
padding: 16rpx 24rpx;
|
||||
padding-bottom: 32rpx;
|
||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.bar-left {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.bar-btn {
|
||||
align-items: center;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.bar-icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.bar-label {
|
||||
font-size: 22rpx;
|
||||
color: #606266;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.bar-right {
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.contact-btn {
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx 40rpx;
|
||||
border-radius: 999rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.contact-text {
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.booking-btn {
|
||||
background-color: #D4A574;
|
||||
padding: 20rpx 48rpx;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
.booking-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
272
前端/pages/cases/list.uvue
Normal file
272
前端/pages/cases/list.uvue
Normal file
@@ -0,0 +1,272 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<!-- 分类筛选 -->
|
||||
<view class="category-bar">
|
||||
<scroll-view class="category-scroll" scroll-x>
|
||||
<view class="category-list">
|
||||
<view
|
||||
class="category-item"
|
||||
:class="{ 'category-active': currentCategory == item.id }"
|
||||
v-for="item in categories"
|
||||
:key="item.id"
|
||||
@click="selectCategory(item.id)"
|
||||
>
|
||||
<text
|
||||
class="category-text"
|
||||
:class="{ 'category-text-active': currentCategory == item.id }"
|
||||
>{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 案例列表 -->
|
||||
<scroll-view
|
||||
class="case-scroll"
|
||||
scroll-y
|
||||
@scrolltolower="loadMore"
|
||||
>
|
||||
<view class="case-list">
|
||||
<case-card
|
||||
v-for="item in caseList"
|
||||
:key="item.id"
|
||||
:caseData="item"
|
||||
@click="goToDetail"
|
||||
></case-card>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="load-status">
|
||||
<text class="load-text" v-if="loading">加载中...</text>
|
||||
<text class="load-text" v-else-if="noMore">没有更多了</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="!loading && caseList.length == 0">
|
||||
<text class="empty-icon">📭</text>
|
||||
<text class="empty-text">暂无相关案例</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部间距 -->
|
||||
<view class="bottom-space"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { getCaseList } from '@/api/index.uts'
|
||||
import { SOFA_CATEGORIES, PAGE_SIZE } from '@/utils/config.uts'
|
||||
|
||||
// 分类类型
|
||||
type CategoryItem = {
|
||||
id : string
|
||||
name : string
|
||||
}
|
||||
|
||||
// 案例类型
|
||||
type CaseItem = {
|
||||
id : string
|
||||
title : string
|
||||
category : string
|
||||
categoryName : string
|
||||
coverImage : string
|
||||
material : string
|
||||
duration : string
|
||||
price : string
|
||||
views : number
|
||||
likes : number
|
||||
}
|
||||
|
||||
// 分类列表
|
||||
const categories = ref<CategoryItem[]>([])
|
||||
|
||||
// 当前分类
|
||||
const currentCategory = ref('all')
|
||||
|
||||
// 案例列表
|
||||
const caseList = ref<CaseItem[]>([])
|
||||
|
||||
// 分页
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
const noMore = ref(false)
|
||||
|
||||
// 初始化分类
|
||||
const initCategories = () => {
|
||||
categories.value = SOFA_CATEGORIES.map((item) : CategoryItem => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name
|
||||
} as CategoryItem
|
||||
})
|
||||
}
|
||||
|
||||
// 选择分类
|
||||
const selectCategory = (id : string) => {
|
||||
if (currentCategory.value == id) return
|
||||
currentCategory.value = id
|
||||
page.value = 1
|
||||
caseList.value = []
|
||||
noMore.value = false
|
||||
fetchCaseList()
|
||||
}
|
||||
|
||||
// 获取案例列表
|
||||
const fetchCaseList = async () => {
|
||||
if (loading.value || noMore.value) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
category: currentCategory.value,
|
||||
page: page.value,
|
||||
pageSize: PAGE_SIZE
|
||||
} as UTSJSONObject
|
||||
|
||||
const res = await getCaseList(params)
|
||||
const data = res.data as UTSJSONObject
|
||||
// 适应后端返回格式:{ items: [], total: 0, page: 1, limit: 10 }
|
||||
const list = data['items'] as UTSJSONObject[] || []
|
||||
total.value = data['total'] as number || 0
|
||||
|
||||
const newList = list.map((item) : CaseItem => {
|
||||
return {
|
||||
id: item['id'] as string,
|
||||
title: item['title'] as string,
|
||||
category: item['category'] as string,
|
||||
categoryName: item['categoryName'] as string,
|
||||
coverImage: item['coverImage'] as string,
|
||||
material: item['material'] as string,
|
||||
duration: item['duration'] as string,
|
||||
price: item['price'] as string,
|
||||
views: item['views'] as number,
|
||||
likes: item['likes'] as number
|
||||
} as CaseItem
|
||||
})
|
||||
|
||||
if (page.value == 1) {
|
||||
caseList.value = newList
|
||||
} else {
|
||||
caseList.value = [...caseList.value, ...newList]
|
||||
}
|
||||
|
||||
if (caseList.value.length >= total.value) {
|
||||
noMore.value = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取案例列表失败', e)
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
const loadMore = () => {
|
||||
if (!noMore.value && !loading.value) {
|
||||
page.value++
|
||||
fetchCaseList()
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转详情
|
||||
const goToDetail = (id : string) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/cases/detail?id=${id}`
|
||||
})
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
initCategories()
|
||||
fetchCaseList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 分类栏 */
|
||||
.category-bar {
|
||||
background-color: #ffffff;
|
||||
padding: 24rpx 0;
|
||||
border-bottom-width: 1rpx;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #EBEEF5;
|
||||
}
|
||||
|
||||
.category-scroll {
|
||||
flex-direction: row;
|
||||
}
|
||||
.category-scroll ::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.category-list {
|
||||
flex-direction: row;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.category-item {
|
||||
padding: 16rpx 32rpx;
|
||||
margin-right: 16rpx;
|
||||
border-radius: 999rpx;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.category-active {
|
||||
background-color: #D4A574;
|
||||
}
|
||||
|
||||
.category-text {
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.category-text-active {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 案例列表 */
|
||||
.case-scroll {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.case-list {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.load-status {
|
||||
padding: 32rpx 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.load-text {
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
padding: 120rpx 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 底部间距 */
|
||||
.bottom-space {
|
||||
height: 120rpx;
|
||||
}
|
||||
</style>
|
||||
418
前端/pages/index/index.uvue
Normal file
418
前端/pages/index/index.uvue
Normal file
@@ -0,0 +1,418 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<!-- 自定义导航栏 -->
|
||||
<nav-bar title="优艺家沙发翻新"></nav-bar>
|
||||
|
||||
<scroll-view class="page-content" scroll-y>
|
||||
<!-- 轮播图 -->
|
||||
<view class="banner-section">
|
||||
<swiper
|
||||
class="banner-swiper"
|
||||
circular
|
||||
autoplay
|
||||
indicator-dots
|
||||
indicator-color="rgba(255,255,255,0.5)"
|
||||
indicator-active-color="#ffffff"
|
||||
>
|
||||
<swiper-item v-for="item in bannerList" :key="item.id">
|
||||
<image
|
||||
class="banner-image"
|
||||
:src="item.image"
|
||||
mode="aspectFill"
|
||||
@click="handleBannerClick(item)"
|
||||
></image>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
|
||||
<!-- 服务入口 -->
|
||||
<view class="service-section">
|
||||
<view class="service-grid">
|
||||
<service-card
|
||||
v-for="item in serviceTypes"
|
||||
:key="item.id"
|
||||
:id="item.id"
|
||||
:name="item.name"
|
||||
:icon="item.icon"
|
||||
@click="handleServiceClick"
|
||||
></service-card>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 公司优势 -->
|
||||
<view class="advantage-section">
|
||||
<view class="advantage-list">
|
||||
<view class="advantage-item" v-for="item in advantages" :key="item.title">
|
||||
<text class="advantage-icon">{{ item.icon }}</text>
|
||||
<view class="advantage-info">
|
||||
<text class="advantage-title">{{ item.title }}</text>
|
||||
<text class="advantage-desc">{{ item.desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 热门案例 -->
|
||||
<view class="case-section">
|
||||
<section-header
|
||||
title="热门案例"
|
||||
:showMore="true"
|
||||
@more="goToCaseList"
|
||||
></section-header>
|
||||
|
||||
<view class="case-list">
|
||||
<case-card
|
||||
v-for="item in hotCases"
|
||||
:key="item.id"
|
||||
:caseData="item"
|
||||
@click="goToCaseDetail"
|
||||
></case-card>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 预约入口 -->
|
||||
<view class="booking-section" @click="goToBooking">
|
||||
<view class="booking-content">
|
||||
<view class="booking-left">
|
||||
<text class="booking-title">免费上门评估</text>
|
||||
<text class="booking-desc">专业师傅免费上门,为您的沙发量身定制翻新方案</text>
|
||||
</view>
|
||||
<view class="booking-btn">
|
||||
<text class="booking-btn-text">立即预约</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部间距 -->
|
||||
<view class="bottom-space"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { getBanners, getHotCases } from '@/api/index.uts'
|
||||
import { SERVICE_TYPES } from '@/utils/config.uts'
|
||||
|
||||
// 轮播图类型
|
||||
type BannerItem = {
|
||||
id : string
|
||||
image : string
|
||||
title : string
|
||||
link : string
|
||||
}
|
||||
|
||||
// 案例类型
|
||||
type CaseItem = {
|
||||
id : string
|
||||
title : string
|
||||
category : string
|
||||
categoryName : string
|
||||
coverImage : string
|
||||
material : string
|
||||
duration : string
|
||||
price : string
|
||||
views : number
|
||||
likes : number
|
||||
}
|
||||
|
||||
// 服务类型
|
||||
type ServiceType = {
|
||||
id : string
|
||||
name : string
|
||||
icon : string
|
||||
}
|
||||
|
||||
// 优势类型
|
||||
type AdvantageItem = {
|
||||
icon : string
|
||||
title : string
|
||||
desc : string
|
||||
}
|
||||
|
||||
// 轮播图数据
|
||||
const bannerList = ref<BannerItem[]>([])
|
||||
|
||||
// 服务类型
|
||||
const serviceTypes = ref<ServiceType[]>([])
|
||||
|
||||
// 热门案例
|
||||
const hotCases = ref<CaseItem[]>([])
|
||||
|
||||
// 公司优势
|
||||
const advantages = ref<AdvantageItem[]>([
|
||||
{ icon: '👨🔧', title: '专业团队', desc: '10年+经验' },
|
||||
{ icon: '✅', title: '品质保证', desc: '质保2年' },
|
||||
{ icon: '🚗', title: '上门服务', desc: '免费评估' },
|
||||
{ icon: '💰', title: '价格透明', desc: '无隐形消费' }
|
||||
])
|
||||
|
||||
// 初始化服务类型
|
||||
const initServiceTypes = () => {
|
||||
serviceTypes.value = SERVICE_TYPES.map((item) : ServiceType => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
icon: item.icon
|
||||
} as ServiceType
|
||||
})
|
||||
}
|
||||
|
||||
// 获取轮播图
|
||||
const fetchBanners = async () => {
|
||||
try {
|
||||
const res = await getBanners()
|
||||
const data = res.data as UTSJSONObject[]
|
||||
bannerList.value = data.map((item) : BannerItem => {
|
||||
return {
|
||||
id: item['id'] as string,
|
||||
image: item['image'] as string,
|
||||
title: item['title'] as string,
|
||||
link: item['link'] as string
|
||||
} as BannerItem
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('获取轮播图失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取热门案例
|
||||
const fetchHotCases = async () => {
|
||||
try {
|
||||
const res = await getHotCases()
|
||||
const data = res.data as UTSJSONObject
|
||||
// 适应后端返回格式:{ items: [], total: 0 }
|
||||
const list = data['items'] as UTSJSONObject[] || []
|
||||
hotCases.value = list.map((item) : CaseItem => {
|
||||
return {
|
||||
id: item['id'] as string,
|
||||
title: item['title'] as string,
|
||||
category: item['category'] as string,
|
||||
categoryName: item['categoryName'] as string,
|
||||
coverImage: item['coverImage'] as string,
|
||||
material: item['material'] as string,
|
||||
duration: item['duration'] as string,
|
||||
price: item['price'] as string,
|
||||
views: item['views'] as number,
|
||||
likes: item['likes'] as number
|
||||
} as CaseItem
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('获取热门案例失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 轮播图点击
|
||||
const handleBannerClick = (item : BannerItem) => {
|
||||
if (item.link != '') {
|
||||
uni.navigateTo({
|
||||
url: item.link,
|
||||
fail: () => {
|
||||
uni.switchTab({
|
||||
url: item.link
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 服务点击
|
||||
const handleServiceClick = (id : string) => {
|
||||
uni.switchTab({
|
||||
url: '/pages/service/index'
|
||||
})
|
||||
}
|
||||
|
||||
// 查看更多案例
|
||||
const goToCaseList = () => {
|
||||
uni.switchTab({
|
||||
url: '/pages/cases/list'
|
||||
})
|
||||
}
|
||||
|
||||
// 查看案例详情
|
||||
const goToCaseDetail = (id : string) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/cases/detail?id=${id}`
|
||||
})
|
||||
}
|
||||
|
||||
// 去预约
|
||||
const goToBooking = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/booking/index'
|
||||
})
|
||||
}
|
||||
|
||||
// API测试方法
|
||||
const testAPI = async () => {
|
||||
try {
|
||||
console.log('开始API对接测试...')
|
||||
|
||||
// 测试轮播图(使用Mock数据)
|
||||
const bannerRes = await getBanners()
|
||||
console.log('轮播图API响应:', bannerRes)
|
||||
|
||||
// 测试案例列表
|
||||
const caseRes = await getCaseList()
|
||||
console.log('案例列表API响应:', caseRes)
|
||||
|
||||
uni.showToast({
|
||||
title: 'API测试完成,请查看控制台',
|
||||
icon: 'success'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('API测试失败:', error)
|
||||
uni.showToast({
|
||||
title: 'API测试失败',
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
initServiceTypes()
|
||||
fetchBanners()
|
||||
fetchHotCases()
|
||||
|
||||
// 开发环境下自动进行API测试
|
||||
// testAPI()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 轮播图 */
|
||||
.banner-section {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.banner-swiper {
|
||||
height: 320rpx;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 服务入口 */
|
||||
.service-section {
|
||||
padding: 0 24rpx 24rpx;
|
||||
}
|
||||
|
||||
.service-grid {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx 16rpx;
|
||||
}
|
||||
|
||||
/* 公司优势 */
|
||||
.advantage-section {
|
||||
padding: 0 24rpx 24rpx;
|
||||
}
|
||||
|
||||
.advantage-list {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.advantage-item {
|
||||
width: 50%;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
|
||||
.advantage-icon {
|
||||
font-size: 48rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.advantage-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.advantage-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.advantage-desc {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 热门案例 */
|
||||
.case-section {
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.case-list {
|
||||
padding-bottom: 24rpx;
|
||||
}
|
||||
|
||||
/* 预约入口 */
|
||||
.booking-section {
|
||||
margin: 0 24rpx 24rpx;
|
||||
background: linear-gradient(135deg, #D4A574 0%, #B8895A 100%);
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.booking-content {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.booking-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.booking-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.booking-desc {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.booking-btn {
|
||||
background-color: #ffffff;
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 999rpx;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.booking-btn-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #D4A574;
|
||||
}
|
||||
|
||||
/* 底部间距 */
|
||||
.bottom-space {
|
||||
height: 120rpx;
|
||||
}
|
||||
</style>
|
||||
537
前端/pages/service/index.uvue
Normal file
537
前端/pages/service/index.uvue
Normal file
@@ -0,0 +1,537 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view class="page-scroll" scroll-y>
|
||||
<!-- 顶部横幅 -->
|
||||
<view class="banner">
|
||||
<view class="banner-content">
|
||||
<text class="banner-title">专业沙发翻新服务</text>
|
||||
<text class="banner-desc">让旧沙发焕发新生</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务类型 -->
|
||||
<view class="section">
|
||||
<section-header title="服务类型"></section-header>
|
||||
<view class="service-grid">
|
||||
<view
|
||||
class="service-item"
|
||||
v-for="item in serviceTypes"
|
||||
:key="item.id"
|
||||
@click="handleServiceClick(item)"
|
||||
>
|
||||
<view class="service-icon-bg">
|
||||
<text class="service-icon">{{ item.emoji }}</text>
|
||||
</view>
|
||||
<text class="service-name">{{ item.name }}</text>
|
||||
<text class="service-desc">{{ item.desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务流程 -->
|
||||
<view class="section">
|
||||
<section-header title="服务流程"></section-header>
|
||||
<view class="process-list">
|
||||
<view class="process-item" v-for="(item, index) in processList" :key="item.step">
|
||||
<view class="process-step">
|
||||
<text class="step-num">{{ item.step }}</text>
|
||||
</view>
|
||||
<view class="process-content">
|
||||
<text class="process-title">{{ item.title }}</text>
|
||||
<text class="process-desc">{{ item.description }}</text>
|
||||
</view>
|
||||
<view class="process-line" v-if="index < processList.length - 1"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 材质说明 -->
|
||||
<view class="section">
|
||||
<section-header title="材质说明"></section-header>
|
||||
<view class="material-list">
|
||||
<view class="material-item" v-for="item in materials" :key="item.name">
|
||||
<view class="material-header">
|
||||
<text class="material-name">{{ item.name }}</text>
|
||||
<text class="material-price">{{ item.price }}</text>
|
||||
</view>
|
||||
<text class="material-desc">{{ item.desc }}</text>
|
||||
<view class="material-tags">
|
||||
<text class="material-tag" v-for="tag in item.tags" :key="tag">{{ tag }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 常见问题 -->
|
||||
<view class="section">
|
||||
<section-header title="常见问题"></section-header>
|
||||
<view class="faq-list">
|
||||
<view
|
||||
class="faq-item"
|
||||
v-for="(item, index) in faqList"
|
||||
:key="index"
|
||||
@click="toggleFaq(index)"
|
||||
>
|
||||
<view class="faq-header">
|
||||
<text class="faq-question">{{ item.question }}</text>
|
||||
<text class="faq-arrow">{{ item.expanded ? '−' : '+' }}</text>
|
||||
</view>
|
||||
<view class="faq-answer" v-if="item.expanded">
|
||||
<text class="faq-answer-text">{{ item.answer }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部间距 -->
|
||||
<view class="bottom-space"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部预约按钮 -->
|
||||
<view class="bottom-bar">
|
||||
<view class="bar-info">
|
||||
<text class="bar-title">免费上门评估</text>
|
||||
<text class="bar-desc">专业师傅为您量身定制方案</text>
|
||||
</view>
|
||||
<view class="bar-btn" @click="goToBooking">
|
||||
<text class="bar-btn-text">立即预约</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { getServiceProcess } from '@/api/index.uts'
|
||||
|
||||
// 服务类型
|
||||
type ServiceType = {
|
||||
id : string
|
||||
name : string
|
||||
desc : string
|
||||
emoji : string
|
||||
}
|
||||
|
||||
// 流程类型
|
||||
type ProcessItem = {
|
||||
step : number
|
||||
title : string
|
||||
description : string
|
||||
}
|
||||
|
||||
// 材质类型
|
||||
type MaterialItem = {
|
||||
name : string
|
||||
price : string
|
||||
desc : string
|
||||
tags : string[]
|
||||
}
|
||||
|
||||
// FAQ类型
|
||||
type FaqItem = {
|
||||
question : string
|
||||
answer : string
|
||||
expanded : boolean
|
||||
}
|
||||
|
||||
// 服务类型数据
|
||||
const serviceTypes = ref<ServiceType[]>([
|
||||
{ id: 'repair', name: '局部修复', desc: '破损、划痕修复', emoji: '🔧' },
|
||||
{ id: 'recolor', name: '改色翻新', desc: '皮面改色换新', emoji: '🎨' },
|
||||
{ id: 'refurbish', name: '整体翻新', desc: '全面翻新升级', emoji: '✨' },
|
||||
{ id: 'custom', name: '定制换皮', desc: '个性化定制', emoji: '💎' }
|
||||
])
|
||||
|
||||
// 服务流程
|
||||
const processList = ref<ProcessItem[]>([])
|
||||
|
||||
// 材质说明
|
||||
const materials = ref<MaterialItem[]>([
|
||||
{
|
||||
name: '头层牛皮',
|
||||
price: '¥800-1500/平',
|
||||
desc: '采用进口头层牛皮,质地柔软,透气性好,使用寿命长',
|
||||
tags: ['进口原料', '透气舒适', '耐用耐磨']
|
||||
},
|
||||
{
|
||||
name: '二层牛皮',
|
||||
price: '¥400-800/平',
|
||||
desc: '经济实惠的选择,经过特殊处理后外观与头层相近',
|
||||
tags: ['性价比高', '外观精美', '易打理']
|
||||
},
|
||||
{
|
||||
name: '科技布',
|
||||
price: '¥200-500/平',
|
||||
desc: '新型环保面料,防水防污,清洁方便,触感舒适',
|
||||
tags: ['防水防污', '易清洁', '环保健康']
|
||||
},
|
||||
{
|
||||
name: '棉麻布艺',
|
||||
price: '¥150-400/平',
|
||||
desc: '天然面料,透气舒适,适合追求自然风格的客户',
|
||||
tags: ['天然环保', '透气清爽', '风格多样']
|
||||
}
|
||||
])
|
||||
|
||||
// 常见问题
|
||||
const faqList = ref<FaqItem[]>([
|
||||
{
|
||||
question: '翻新需要多长时间?',
|
||||
answer: '根据沙发的大小和翻新程度不同,一般需要3-10个工作日。局部修复1-3天,整体翻新5-10天。',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '翻新后能保证多久?',
|
||||
answer: '我们提供2年质保服务,质保期内如有非人为损坏,免费维修。头层牛皮正常使用可达10年以上。',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '可以上门取送吗?',
|
||||
answer: '是的,我们提供免费上门取送服务(市区范围内),郊区会收取少量运输费用。',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '如何报价?',
|
||||
answer: '我们提供免费上门评估服务,师傅会根据沙发的实际情况给出详细报价,价格透明无隐形消费。',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '定金和尾款如何支付?',
|
||||
answer: '确认订单后支付30%定金,翻新完成验收满意后支付尾款。支持微信、支付宝、银行转账等多种方式。',
|
||||
expanded: false
|
||||
}
|
||||
])
|
||||
|
||||
// 获取服务流程
|
||||
const fetchServiceProcess = async () => {
|
||||
try {
|
||||
const res = await getServiceProcess()
|
||||
const data = res.data as UTSJSONObject[]
|
||||
processList.value = data.map((item) : ProcessItem => {
|
||||
return {
|
||||
step: item['step'] as number,
|
||||
title: item['title'] as string,
|
||||
description: item['description'] as string
|
||||
} as ProcessItem
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('获取服务流程失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 切换FAQ展开
|
||||
const toggleFaq = (index : number) => {
|
||||
faqList.value[index].expanded = !faqList.value[index].expanded
|
||||
}
|
||||
|
||||
// 服务点击
|
||||
const handleServiceClick = (item : ServiceType) => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/booking/index'
|
||||
})
|
||||
}
|
||||
|
||||
// 去预约
|
||||
const goToBooking = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/booking/index'
|
||||
})
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
fetchServiceProcess()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-scroll {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 顶部横幅 */
|
||||
.banner {
|
||||
height: 280rpx;
|
||||
background: linear-gradient(135deg, #D4A574 0%, #B8895A 100%);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.banner-content {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.banner-desc {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* 通用section */
|
||||
.section {
|
||||
padding: 0 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
/* 服务类型 */
|
||||
.service-grid {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.service-item {
|
||||
width: 50%;
|
||||
padding: 24rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.service-icon-bg {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
background-color: #FDF6EE;
|
||||
border-radius: 50%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.service-icon {
|
||||
font-size: 48rpx;
|
||||
}
|
||||
|
||||
.service-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.service-desc {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 服务流程 */
|
||||
.process-list {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.process-item {
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
padding-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.process-step {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
background-color: #D4A574;
|
||||
border-radius: 50%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.step-num {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.process-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.process-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.process-desc {
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.process-line {
|
||||
position: absolute;
|
||||
left: 22rpx;
|
||||
top: 56rpx;
|
||||
width: 4rpx;
|
||||
height: 60rpx;
|
||||
background-color: #E8C9A8;
|
||||
}
|
||||
|
||||
/* 材质说明 */
|
||||
.material-list {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.material-item {
|
||||
padding: 32rpx;
|
||||
border-bottom-width: 1rpx;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #EBEEF5;
|
||||
}
|
||||
|
||||
.material-item:last-child {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
.material-header {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.material-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.material-price {
|
||||
font-size: 28rpx;
|
||||
color: #D4A574;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.material-desc {
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 16rpx;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
|
||||
.material-tags {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.material-tag {
|
||||
font-size: 22rpx;
|
||||
color: #D4A574;
|
||||
background-color: #FDF6EE;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 16rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
/* 常见问题 */
|
||||
.faq-list {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.faq-item {
|
||||
padding: 32rpx;
|
||||
border-bottom-width: 1rpx;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #EBEEF5;
|
||||
}
|
||||
|
||||
.faq-item:last-child {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
.faq-header {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.faq-question {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.faq-arrow {
|
||||
font-size: 36rpx;
|
||||
color: #909399;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.faq-answer {
|
||||
margin-top: 16rpx;
|
||||
padding-top: 16rpx;
|
||||
border-top-width: 1rpx;
|
||||
border-top-style: dashed;
|
||||
border-top-color: #EBEEF5;
|
||||
}
|
||||
|
||||
.faq-answer-text {
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
line-height: 44rpx;
|
||||
}
|
||||
|
||||
/* 底部间距 */
|
||||
.bottom-space {
|
||||
height: 160rpx;
|
||||
}
|
||||
|
||||
/* 底部操作栏 */
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index:2;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: #ffffff;
|
||||
padding: 20rpx 32rpx;
|
||||
padding-bottom: 40rpx;
|
||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.bar-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.bar-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.bar-desc {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.bar-btn {
|
||||
background-color: #D4A574;
|
||||
padding: 24rpx 56rpx;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
.bar-btn-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
488
前端/pages/user/index.uvue
Normal file
488
前端/pages/user/index.uvue
Normal file
@@ -0,0 +1,488 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view class="page-scroll" scroll-y>
|
||||
<!-- 用户信息 -->
|
||||
<view class="user-section">
|
||||
<view class="user-card">
|
||||
<view class="avatar-wrapper">
|
||||
<image
|
||||
class="user-avatar"
|
||||
:src="userInfo.avatar || '/static/images/default-avatar.png'"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
<view class="user-info">
|
||||
<text class="user-name">{{ userInfo.nickName || '点击登录' }}</text>
|
||||
<text class="user-phone" v-if="userInfo.phone">{{ userInfo.phone }}</text>
|
||||
</view>
|
||||
<view class="user-action" @click="handleLogin" v-if="!isLoggedIn">
|
||||
<text class="action-text">登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能菜单 -->
|
||||
<view class="menu-section">
|
||||
<view class="menu-group">
|
||||
<view class="menu-item" @click="goToBookingList">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">📋</text>
|
||||
<text class="menu-text">我的预约</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<text class="menu-badge" v-if="bookingCount > 0">{{ bookingCount }}</text>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="goToFavorites">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">❤️</text>
|
||||
<text class="menu-text">我的收藏</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<text class="menu-badge" v-if="favoriteCount > 0">{{ favoriteCount }}</text>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-group">
|
||||
<view class="menu-item" @click="goToAbout">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">🏠</text>
|
||||
<text class="menu-text">关于我们</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="callService">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">📞</text>
|
||||
<text class="menu-text">联系客服</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<text class="menu-value">400-888-8888</text>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="openFeedback">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">💬</text>
|
||||
<text class="menu-text">意见反馈</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-group">
|
||||
<view class="menu-item" @click="checkUpdate">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">🔄</text>
|
||||
<text class="menu-text">检查更新</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<text class="menu-value">v1.0.0</text>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="clearCache">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">🧹</text>
|
||||
<text class="menu-text">清除缓存</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<text class="menu-value">{{ cacheSize }}</text>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录 -->
|
||||
<view class="logout-section" v-if="isLoggedIn">
|
||||
<view class="logout-btn" @click="handleLogout">
|
||||
<text class="logout-text">退出登录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部间距 -->
|
||||
<view class="bottom-space"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { STORAGE_KEYS } from '@/utils/config.uts'
|
||||
|
||||
// 用户信息类型
|
||||
type UserInfo = {
|
||||
id : string
|
||||
nickName : string
|
||||
avatar : string
|
||||
phone : string
|
||||
}
|
||||
|
||||
// 用户信息
|
||||
const userInfo = ref<UserInfo>({
|
||||
id: '',
|
||||
nickName: '',
|
||||
avatar: '',
|
||||
phone: ''
|
||||
})
|
||||
|
||||
// 是否登录
|
||||
const isLoggedIn = ref(false)
|
||||
|
||||
// 预约数量
|
||||
const bookingCount = ref(0)
|
||||
|
||||
// 收藏数量
|
||||
const favoriteCount = ref(0)
|
||||
|
||||
// 缓存大小
|
||||
const cacheSize = ref('0KB')
|
||||
|
||||
// 检查登录状态
|
||||
const checkLoginStatus = () => {
|
||||
const token = uni.getStorageSync(STORAGE_KEYS.TOKEN) as string
|
||||
isLoggedIn.value = token != ''
|
||||
|
||||
if (isLoggedIn.value) {
|
||||
// 获取用户信息
|
||||
const info = uni.getStorageSync(STORAGE_KEYS.USER_INFO) as UTSJSONObject | null
|
||||
if (info != null) {
|
||||
userInfo.value = {
|
||||
id: (info['id'] ?? '') as string,
|
||||
nickName: (info['nickName'] ?? '') as string,
|
||||
avatar: (info['avatar'] ?? '') as string,
|
||||
phone: (info['phone'] ?? '') as string
|
||||
} as UserInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取缓存大小
|
||||
const getCacheSize = () => {
|
||||
uni.getStorageInfo({
|
||||
success: (res) => {
|
||||
const size = res.currentSize
|
||||
if (size < 1024) {
|
||||
cacheSize.value = `${size}KB`
|
||||
} else {
|
||||
cacheSize.value = `${(size / 1024).toFixed(2)}MB`
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 登录
|
||||
const handleLogin = () => {
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.getUserProfile({
|
||||
desc: '用于完善用户资料',
|
||||
success: (res) => {
|
||||
userInfo.value = {
|
||||
id: '',
|
||||
nickName: res.userInfo.nickName,
|
||||
avatar: res.userInfo.avatarUrl,
|
||||
phone: ''
|
||||
} as UserInfo
|
||||
|
||||
// 保存用户信息
|
||||
uni.setStorageSync(STORAGE_KEYS.USER_INFO, {
|
||||
nickName: res.userInfo.nickName,
|
||||
avatar: res.userInfo.avatarUrl
|
||||
} as UTSJSONObject)
|
||||
uni.setStorageSync(STORAGE_KEYS.TOKEN, 'mock_token_' + Date.now().toString())
|
||||
isLoggedIn.value = true
|
||||
|
||||
uni.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success'
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '登录失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
uni.showToast({
|
||||
title: '请在微信小程序中登录',
|
||||
icon: 'none'
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
const handleLogout = () => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.removeStorageSync(STORAGE_KEYS.TOKEN)
|
||||
uni.removeStorageSync(STORAGE_KEYS.USER_INFO)
|
||||
isLoggedIn.value = false
|
||||
userInfo.value = {
|
||||
id: '',
|
||||
nickName: '',
|
||||
avatar: '',
|
||||
phone: ''
|
||||
} as UserInfo
|
||||
|
||||
uni.showToast({
|
||||
title: '已退出登录',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转预约列表
|
||||
const goToBookingList = () => {
|
||||
uni.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转收藏列表
|
||||
const goToFavorites = () => {
|
||||
uni.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 关于我们
|
||||
const goToAbout = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/about/index'
|
||||
})
|
||||
}
|
||||
|
||||
// 联系客服
|
||||
const callService = () => {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: '400-888-8888',
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '拨打电话失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 意见反馈
|
||||
const openFeedback = () => {
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序可以使用feedback
|
||||
// #endif
|
||||
uni.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 检查更新
|
||||
const checkUpdate = () => {
|
||||
uni.showToast({
|
||||
title: '已是最新版本',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
const clearCache = () => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要清除缓存吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.clearStorage({
|
||||
success: () => {
|
||||
cacheSize.value = '0KB'
|
||||
uni.showToast({
|
||||
title: '清除成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
checkLoginStatus()
|
||||
getCacheSize()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page {
|
||||
flex: 1;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-scroll {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 用户信息 */
|
||||
.user-section {
|
||||
background: linear-gradient(135deg, #D4A574 0%, #B8895A 100%);
|
||||
padding: 48rpx 32rpx 64rpx;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
border-width: 4rpx;
|
||||
border-style: solid;
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.user-phone {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.user-action {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 28rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 菜单区域 */
|
||||
.menu-section {
|
||||
margin-top: -32rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.menu-group {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx;
|
||||
border-bottom-width: 1rpx;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #EBEEF5;
|
||||
}
|
||||
|
||||
.menu-item:last-child {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
.menu-left {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
font-size: 40rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.menu-right {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.menu-value {
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.menu-badge {
|
||||
background-color: #F56C6C;
|
||||
color: #ffffff;
|
||||
font-size: 22rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 999rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.menu-arrow {
|
||||
font-size: 32rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 退出登录 */
|
||||
.logout-section {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logout-text {
|
||||
font-size: 30rpx;
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
/* 底部间距 */
|
||||
.bottom-space {
|
||||
height: 120rpx;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user