初始化参股

This commit is contained in:
2026-01-27 18:06:04 +08:00
commit 2774a539bf
254 changed files with 33255 additions and 0 deletions

48
前端/App.uvue Normal file
View File

@@ -0,0 +1,48 @@
<script lang="uts">
// #ifdef APP-ANDROID || APP-HARMONY
let firstBackTime = 0
// #endif
export default {
onLaunch() {
console.log('App Launch')
},
onShow() {
console.log('App Show')
},
onHide() {
console.log('App Hide')
},
// #ifdef APP-ANDROID || APP-HARMONY
onLastPageBackPress() {
console.log('App LastPageBackPress')
if (firstBackTime == 0) {
uni.showToast({
title: '再按一次退出应用',
position: 'bottom',
})
firstBackTime = Date.now()
setTimeout(() => {
firstBackTime = 0
}, 2000)
} else if (Date.now() - firstBackTime < 2000) {
firstBackTime = Date.now()
uni.exit()
}
},
// #endif
onExit() {
console.log('App Exit')
},
}
</script>
<style>
/*每个页面公共css */
.uni-row {
flex-direction: row;
}
.uni-column {
flex-direction: column;
}
</style>

142
前端/README.md Normal file
View File

@@ -0,0 +1,142 @@
# 优艺家沙发翻新小程序
## 项目简介
基于 uni-app x 开发的沙发翻新案例展示小程序支持微信小程序、H5等多端运行。
## 技术栈
- **框架**uni-app x (Vue 3 + UTS)
- **样式**SCSS
- **状态管理**:组合式 API (Composition API)
## 项目结构
```
├── api/ # API接口定义
│ └── index.uts # 接口统一管理
├── components/ # 公共组件
│ ├── case-card/ # 案例卡片组件
│ ├── before-after/ # 翻新前后对比组件
│ ├── nav-bar/ # 自定义导航栏
│ ├── section-header/ # 区块标题组件
│ └── service-card/ # 服务卡片组件
├── pages/ # 页面文件
│ ├── index/ # 首页
│ ├── cases/ # 案例模块
│ │ ├── list.uvue # 案例列表
│ │ └── detail.uvue # 案例详情
│ ├── service/ # 服务介绍
│ ├── about/ # 关于我们
│ ├── booking/ # 预约咨询
│ └── user/ # 用户中心
├── static/ # 静态资源
│ ├── icons/ # 图标文件
│ ├── images/ # 图片资源
│ └── mock/ # Mock图片
├── utils/ # 工具函数
│ ├── config.uts # 配置文件
│ ├── request.uts # 网络请求封装
│ └── mock.uts # Mock数据
├── pages.json # 页面配置
├── manifest.json # 应用配置
└── uni.scss # 全局样式变量
```
## 功能模块
### 1. 首页
- 轮播图展示
- 服务类型入口
- 公司优势展示
- 热门案例推荐
- 预约入口
### 2. 案例展示
- 分类筛选(皮沙发/布艺沙发/功能沙发等)
- 案例列表(瀑布流/列表展示)
- 案例详情(翻新前后对比、详细信息)
### 3. 服务介绍
- 服务类型说明
- 服务流程展示
- 材质说明
- 常见问题
### 4. 预约咨询
- 预约表单
- 图片上传
- 表单验证
### 5. 用户中心
- 用户登录
- 我的预约
- 我的收藏
- 设置功能
## 开发说明
### 安装依赖
```bash
# 使用 HBuilderX 打开项目
```
### 运行项目
1. 使用 HBuilderX 打开项目
2. 点击运行 -> 运行到小程序模拟器 -> 微信开发者工具
### Mock 数据
项目默认使用 Mock 数据,修改 `utils/config.uts` 中的 `useMock` 变量:
```typescript
// 是否使用Mock数据
export const useMock = true // 开发阶段
export const useMock = false // 对接后端
```
### 后端接口
项目已预留后端接口,接口定义在 `api/index.uts` 中:
| 接口 | 方法 | 说明 |
|------|------|------|
| /banners | GET | 获取轮播图 |
| /cases | GET | 获取案例列表 |
| /cases/:id | GET | 获取案例详情 |
| /cases/hot | GET | 获取热门案例 |
| /service/process | GET | 获取服务流程 |
| /company/info | GET | 获取公司信息 |
| /booking | POST | 提交预约 |
| /user/info | GET | 获取用户信息 |
| /user/favorites | GET/POST | 收藏管理 |
## 后续开发计划
### 前端待完善
- [ ] 添加 TabBar 图标
- [ ] 添加案例图片资源
- [ ] 完善图片预览功能
- [ ] 添加下拉刷新
- [ ] 完善分享功能
- [ ] 添加骨架屏加载
### 后端开发
- [ ] 用户认证模块
- [ ] 案例管理接口
- [ ] 预约管理接口
- [ ] 文件上传接口
- [ ] 管理后台
## 注意事项
1. 运行前请确保已安装 HBuilderX
2. 微信小程序需配置 appid
3. 图片资源需自行准备
4. 正式上线前关闭 Mock 数据
## 联系方式
如有问题,请联系开发团队。

126
前端/api/index.uts Normal file
View File

@@ -0,0 +1,126 @@
/**
* API接口统一管理
*/
import { get, post } from '../utils/request.uts'
/**
* 获取轮播图 (临时从配置获取,后续可从后端获取)
*/
export const getBanners = () => {
// 由于后端暂时没有轮播图API先返回固定数据
return Promise.resolve({
code: 0,
message: 'success',
data: [
{
id: '1',
image: '/static/mock/banner1.svg',
title: '专业沙发翻新服务',
link: '/pages/service/index'
},
{
id: '2',
image: '/static/mock/banner2.svg',
title: '十年品质保证',
link: '/pages/about/index'
},
{
id: '3',
image: '/static/mock/banner3.svg',
title: '免费上门评估',
link: '/pages/booking/index'
}
]
})
}
/**
* 获取案例列表
*/
export const getCaseList = (params ?: UTSJSONObject) => {
// 后端使用page和limit参数需要转换
const queryParams = params ? {
page: params['page'] || 1,
limit: params['pageSize'] || params['limit'] || 10,
serviceType: params['category'], // 后端使用serviceType
status: 'published' // 只获取已发布的案例
} : {}
return get('/cases', queryParams as UTSJSONObject)
}
/**
* 获取案例详情
*/
export const getCaseDetail = (id : string) => {
return get(`/cases/${id}`)
}
/**
* 获取热门案例 (临时使用前4条案例)
*/
export const getHotCases = () => {
return get('/cases', { limit: 4, status: 'published' } as UTSJSONObject)
}
/**
* 获取服务流程 (使用服务列表替代)
*/
export const getServiceProcess = () => {
return get('/services')
}
/**
* 获取有效服务列表
*/
export const getActiveServices = () => {
return get('/services/active')
}
/**
* 获取公司信息
*/
export const getCompanyInfo = () => {
return get('/company/info')
}
/**
* 提交预约
*/
export const submitBooking = (data : UTSJSONObject) => {
return post('/booking', data)
}
/**
* 获取用户信息
*/
export const getUserInfo = () => {
return get('/user/info')
}
/**
* 获取用户收藏列表
*/
export const getFavorites = () => {
return get('/user/favorites')
}
/**
* 添加收藏
*/
export const addFavorite = (caseId : string) => {
return post('/user/favorites', { caseId: caseId } as UTSJSONObject)
}
/**
* 取消收藏
*/
export const removeFavorite = (caseId : string) => {
return post('/user/favorites/remove', { caseId: caseId } as UTSJSONObject)
}
/**
* 获取预约记录
*/
export const getBookingList = () => {
return get('/user/bookings')
}

View File

@@ -0,0 +1,125 @@
<template>
<view class="before-after">
<view class="ba-title" v-if="showTitle">
<text class="ba-title-text">翻新前后对比</text>
</view>
<view class="ba-container">
<!-- 翻新前 -->
<view class="ba-item" @click="previewImage(beforeImage, 'before')">
<view class="ba-label before-label">
<text class="ba-label-text">翻新前</text>
</view>
<image
class="ba-image"
:src="beforeImage"
mode="aspectFill"
></image>
</view>
<!-- 箭头 -->
<view class="ba-arrow">
<text class="ba-arrow-text">→</text>
</view>
<!-- 翻新后 -->
<view class="ba-item" @click="previewImage(afterImage, 'after')">
<view class="ba-label after-label">
<text class="ba-label-text">翻新后</text>
</view>
<image
class="ba-image"
:src="afterImage"
mode="aspectFill"
></image>
</view>
</view>
</view>
</template>
<script setup lang="uts">
const props = defineProps<{
beforeImage : string
afterImage : string
showTitle ?: boolean
}>()
// 预览图片
const previewImage = (url : string, type : string) => {
const urls = type == 'before' ? [props.beforeImage] : [props.afterImage]
uni.previewImage({
current: url,
urls: urls
})
}
</script>
<style lang="scss">
.before-after {
background-color: #ffffff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 24rpx;
}
.ba-title {
margin-bottom: 24rpx;
}
.ba-title-text {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
.ba-container {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.ba-item {
flex: 1;
position: relative;
border-radius: 12rpx;
overflow: hidden;
}
.ba-image {
width: 100%;
height: 280rpx;
border-radius: 12rpx;
}
.ba-label {
position: absolute;
bottom: 16rpx;
left: 16rpx;
padding: 8rpx 16rpx;
border-radius: 8rpx;
z-index: 1;
}
.before-label {
background-color: rgba(144, 147, 153, 0.9);
}
.after-label {
background-color: rgba(212, 165, 116, 0.9);
}
.ba-label-text {
font-size: 22rpx;
color: #ffffff;
}
.ba-arrow {
padding: 0 16rpx;
}
.ba-arrow-text {
font-size: 40rpx;
color: #D4A574;
font-weight: bold;
}
</style>

View File

@@ -0,0 +1,154 @@
<template>
<view class="case-card" @click="handleClick">
<!-- 封面图 -->
<view class="card-image-wrapper">
<image
class="card-image"
:src="caseData.coverImage"
mode="aspectFill"
></image>
<view class="card-category">{{ caseData.categoryName }}</view>
</view>
<!-- 内容区域 -->
<view class="card-content">
<text class="card-title">{{ caseData.title }}</text>
<view class="card-info">
<view class="info-item">
<text class="info-label">材质:</text>
<text class="info-value">{{ caseData.material }}</text>
</view>
<view class="info-item">
<text class="info-label">工期:</text>
<text class="info-value">{{ caseData.duration }}</text>
</view>
</view>
<view class="card-footer">
<text class="card-price">{{ caseData.price }}</text>
<view class="card-stats">
<text class="stat-item">👁 {{ caseData.views }}</text>
<text class="stat-item">❤ {{ caseData.likes }}</text>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
// 定义Props类型
type CaseItem = {
id : string
title : string
category : string
categoryName : string
coverImage : string
material : string
duration : string
price : string
views : number
likes : number
}
const props = defineProps<{
caseData : CaseItem
}>()
const emit = defineEmits<{
(e : 'click', id : string) : void
}>()
const handleClick = () => {
emit('click', props.caseData.id)
}
</script>
<style lang="scss">
.case-card {
background-color: #ffffff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
margin-bottom: 24rpx;
}
.card-image-wrapper {
position: relative;
width: 100%;
height: 360rpx;
}
.card-image {
width: 100%;
height: 100%;
}
.card-category {
position: absolute;
top: 16rpx;
left: 16rpx;
background-color: rgba(212, 165, 116, 0.9);
color: #ffffff;
font-size: 22rpx;
padding: 8rpx 16rpx;
border-radius: 8rpx;
}
.card-content {
padding: 24rpx;
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
margin-bottom: 16rpx;
lines: 1;
text-overflow: ellipsis;
}
.card-info {
margin-bottom: 16rpx;
}
.info-item {
flex-direction: row;
margin-bottom: 8rpx;
}
.info-label {
font-size: 26rpx;
color: #909399;
}
.info-value {
font-size: 26rpx;
color: #606266;
}
.card-footer {
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: 16rpx;
padding-top: 16rpx;
border-top-width: 1rpx;
border-top-style: solid;
border-top-color: #EBEEF5;
}
.card-price {
font-size: 36rpx;
font-weight: 600;
color: #D4A574;
}
.card-stats {
flex-direction: row;
}
.stat-item {
font-size: 24rpx;
color: #909399;
margin-left: 24rpx;
}
</style>

View File

@@ -0,0 +1,105 @@
<template>
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-content" :style="{ height: navBarHeight + 'px' }">
<!-- 左侧返回按钮 -->
<view class="nav-left" v-if="showBack" @click="handleBack">
<text class="nav-back-icon">←</text>
</view>
<view class="nav-left" v-else></view>
<!-- 标题 -->
<view class="nav-center">
<text class="nav-title" :style="{ color: titleColor }">{{ title }}</text>
</view>
<!-- 右侧插槽 -->
<view class="nav-right">
<slot name="right"></slot>
</view>
</view>
</view>
<!-- 占位高度 -->
<view :style="{ height: (statusBarHeight + navBarHeight) + 'px' }"></view>
</template>
<script setup lang="uts">
const props = defineProps<{
title ?: string
showBack ?: boolean
titleColor ?: string
bgColor ?: string
}>()
// 状态栏高度
const statusBarHeight = ref(20)
// 导航栏高度
const navBarHeight = ref(44)
onMounted(() => {
const sysInfo = uni.getSystemInfoSync()
statusBarHeight.value = sysInfo.statusBarHeight
// #ifdef MP-WEIXIN
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
navBarHeight.value = (menuButtonInfo.top - sysInfo.statusBarHeight) * 2 + menuButtonInfo.height
// #endif
})
const handleBack = () => {
uni.navigateBack({
fail: () => {
uni.switchTab({
url: '/pages/index/index'
})
}
})
}
</script>
<style lang="scss">
.nav-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
background-color: #ffffff;
z-index: 999;
}
.nav-content {
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 24rpx;
}
.nav-left {
width: 80rpx;
flex-direction: row;
align-items: center;
}
.nav-back-icon {
font-size: 40rpx;
color: #333333;
}
.nav-center {
flex: 1;
align-items: center;
justify-content: center;
}
.nav-title {
font-size: 34rpx;
font-weight: 600;
color: #333333;
}
.nav-right {
width: 80rpx;
flex-direction: row;
align-items: center;
justify-content: flex-end;
}
</style>

View File

@@ -0,0 +1,71 @@
<template>
<view class="section-header">
<view class="section-left">
<view class="section-line"></view>
<text class="section-title">{{ title }}</text>
</view>
<view class="section-right" v-if="showMore" @click="handleMore">
<text class="section-more">查看更多</text>
<text class="section-arrow"></text>
</view>
</view>
</template>
<script setup lang="uts">
const props = defineProps<{
title : string
showMore ?: boolean
}>()
const emit = defineEmits<{
(e : 'more') : void
}>()
const handleMore = () => {
emit('more')
}
</script>
<style lang="scss">
.section-header {
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 32rpx 0 24rpx 0;
}
.section-left {
flex-direction: row;
align-items: center;
}
.section-line {
width: 8rpx;
height: 36rpx;
background-color: #D4A574;
border-radius: 4rpx;
margin-right: 16rpx;
}
.section-title {
font-size: 34rpx;
font-weight: 600;
color: #333333;
}
.section-right {
flex-direction: row;
align-items: center;
}
.section-more {
font-size: 26rpx;
color: #909399;
}
.section-arrow {
font-size: 32rpx;
color: #909399;
margin-left: 8rpx;
}
</style>

View File

@@ -0,0 +1,56 @@
<template>
<view class="service-card" @click="handleClick">
<view class="service-icon-wrapper">
<image class="service-icon" :src="icon" mode="aspectFit"></image>
</view>
<text class="service-name">{{ name }}</text>
</view>
</template>
<script setup lang="uts">
const props = defineProps<{
id : string
name : string
icon : string
}>()
const emit = defineEmits<{
(e : 'click', id : string) : void
}>()
const handleClick = () => {
emit('click', props.id)
}
</script>
<style lang="scss">
.service-card {
align-items: center;
justify-content: center;
padding: 24rpx 16rpx;
background-color: #ffffff;
border-radius: 16rpx;
width: 160rpx;
}
.service-icon-wrapper {
width: 80rpx;
height: 80rpx;
background-color: #FDF6EE;
border-radius: 50%;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
}
.service-icon {
width: 48rpx;
height: 48rpx;
}
.service-name {
font-size: 26rpx;
color: #333333;
text-align: center;
}
</style>

58
前端/convert-icons.js Normal file
View File

@@ -0,0 +1,58 @@
/**
* 图标转换脚本
* 将 SVG 图标转换为 PNG 格式
*
* 使用方法:
* 1. npm install sharp
* 2. node convert-icons.js
*/
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');
const iconsDir = path.join(__dirname, 'static/icons');
const outputSize = 81;
const svgFiles = [
'home.svg',
'home-active.svg',
'cases.svg',
'cases-active.svg',
'service.svg',
'service-active.svg',
'user.svg',
'user-active.svg'
];
async function convertSvgToPng(svgFile) {
const inputPath = path.join(iconsDir, svgFile);
const outputPath = path.join(iconsDir, svgFile.replace('.svg', '.png'));
try {
await sharp(inputPath)
.resize(outputSize, outputSize)
.png()
.toFile(outputPath);
console.log(`✓ 转换成功: ${svgFile} -> ${svgFile.replace('.svg', '.png')}`);
} catch (error) {
console.error(`✗ 转换失败: ${svgFile}`, error.message);
}
}
async function main() {
console.log('开始转换图标...\n');
for (const file of svgFiles) {
const filePath = path.join(iconsDir, file);
if (fs.existsSync(filePath)) {
await convertSvgToPng(file);
} else {
console.log(`⚠ 文件不存在: ${file}`);
}
}
console.log('\n转换完成');
}
main();

394
前端/docs/backend-plan.md Normal file
View File

@@ -0,0 +1,394 @@
# 后端开发规划
## 一、技术选型
| 项目 | 技术方案 | 说明 |
|------|----------|------|
| 框架 | Node.js + Express / Koa | 或 Python FastAPI / Java Spring Boot |
| 数据库 | MySQL / PostgreSQL | 关系型数据库存储业务数据 |
| 缓存 | Redis | 缓存热点数据、会话管理 |
| 文件存储 | 阿里云OSS / 腾讯云COS | 图片、文件存储 |
| 认证 | JWT + 微信登录 | 用户身份认证 |
## 二、数据库设计
### 2.1 用户表 (users)
```sql
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
open_id VARCHAR(64) UNIQUE NOT NULL COMMENT '微信openid',
union_id VARCHAR(64) COMMENT '微信unionid',
nick_name VARCHAR(64) COMMENT '昵称',
avatar VARCHAR(255) COMMENT '头像URL',
phone VARCHAR(20) COMMENT '手机号',
gender TINYINT DEFAULT 0 COMMENT '性别 0未知 1男 2女',
status TINYINT DEFAULT 1 COMMENT '状态 0禁用 1正常',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_open_id (open_id),
INDEX idx_phone (phone)
);
```
### 2.2 案例分类表 (categories)
```sql
CREATE TABLE categories (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(32) NOT NULL COMMENT '分类名称',
code VARCHAR(32) UNIQUE NOT NULL COMMENT '分类编码',
icon VARCHAR(255) COMMENT '图标URL',
sort_order INT DEFAULT 0 COMMENT '排序',
status TINYINT DEFAULT 1 COMMENT '状态 0禁用 1正常',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 初始数据
INSERT INTO categories (name, code, sort_order) VALUES
('皮沙发', 'leather', 1),
('布艺沙发', 'fabric', 2),
('功能沙发', 'functional', 3),
('古典沙发', 'antique', 4),
('办公沙发', 'office', 5);
```
### 2.3 案例表 (cases)
```sql
CREATE TABLE cases (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(128) NOT NULL COMMENT '案例标题',
category_id INT NOT NULL COMMENT '分类ID',
cover_image VARCHAR(255) NOT NULL COMMENT '封面图',
before_images JSON COMMENT '翻新前图片JSON数组',
after_images JSON COMMENT '翻新后图片JSON数组',
description TEXT COMMENT '案例描述',
material VARCHAR(128) COMMENT '使用材质',
duration VARCHAR(32) COMMENT '工期',
price VARCHAR(32) COMMENT '参考价格',
views INT DEFAULT 0 COMMENT '浏览量',
likes INT DEFAULT 0 COMMENT '点赞数',
is_hot TINYINT DEFAULT 0 COMMENT '是否热门 0否 1是',
is_recommend TINYINT DEFAULT 0 COMMENT '是否推荐 0否 1是',
status TINYINT DEFAULT 1 COMMENT '状态 0下架 1上架',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_category (category_id),
INDEX idx_status (status),
INDEX idx_hot (is_hot),
INDEX idx_created (created_at)
);
```
### 2.4 预约表 (bookings)
```sql
CREATE TABLE bookings (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
booking_no VARCHAR(32) UNIQUE NOT NULL COMMENT '预约编号',
user_id BIGINT COMMENT '用户ID',
user_name VARCHAR(32) NOT NULL COMMENT '客户姓名',
phone VARCHAR(20) NOT NULL COMMENT '联系电话',
address VARCHAR(255) NOT NULL COMMENT '地址',
sofa_type VARCHAR(32) COMMENT '沙发类型',
problem TEXT COMMENT '问题描述',
images JSON COMMENT '上传图片JSON数组',
status TINYINT DEFAULT 0 COMMENT '状态 0待确认 1已确认 2已上门 3已完成 4已取消',
remark TEXT COMMENT '备注',
assigned_to BIGINT COMMENT '指派给(员工ID)',
visited_at DATETIME COMMENT '上门时间',
completed_at DATETIME COMMENT '完成时间',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user (user_id),
INDEX idx_phone (phone),
INDEX idx_status (status),
INDEX idx_created (created_at)
);
```
### 2.5 收藏表 (favorites)
```sql
CREATE TABLE favorites (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL COMMENT '用户ID',
case_id BIGINT NOT NULL COMMENT '案例ID',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_case (user_id, case_id),
INDEX idx_user (user_id)
);
```
### 2.6 轮播图表 (banners)
```sql
CREATE TABLE banners (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(64) COMMENT '标题',
image VARCHAR(255) NOT NULL COMMENT '图片URL',
link VARCHAR(255) COMMENT '跳转链接',
sort_order INT DEFAULT 0 COMMENT '排序',
status TINYINT DEFAULT 1 COMMENT '状态 0禁用 1正常',
start_time DATETIME COMMENT '开始时间',
end_time DATETIME COMMENT '结束时间',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
```
### 2.7 系统配置表 (settings)
```sql
CREATE TABLE settings (
id INT PRIMARY KEY AUTO_INCREMENT,
setting_key VARCHAR(64) UNIQUE NOT NULL COMMENT '配置键',
setting_value TEXT COMMENT '配置值',
description VARCHAR(255) COMMENT '说明',
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 初始配置
INSERT INTO settings (setting_key, setting_value, description) VALUES
('company_name', '优艺家沙发翻新', '公司名称'),
('company_slogan', '让旧沙发焕发新生', '公司标语'),
('company_description', '优艺家是一家专业从事沙发翻新、维修、改色的服务公司...', '公司介绍'),
('contact_phone', '400-888-8888', '客服电话'),
('contact_wechat', 'youyijia2026', '微信号'),
('company_address', '上海市浦东新区张江高科技园区', '公司地址'),
('work_time', '周一至周日 9:00-18:00', '营业时间');
```
## 三、API接口设计
### 3.1 公共接口
| 接口 | 方法 | 说明 | 认证 |
|------|------|------|------|
| POST /api/auth/wxlogin | POST | 微信登录 | 否 |
| GET /api/banners | GET | 获取轮播图 | 否 |
| GET /api/categories | GET | 获取分类列表 | 否 |
| GET /api/company/info | GET | 获取公司信息 | 否 |
| GET /api/service/process | GET | 获取服务流程 | 否 |
### 3.2 案例接口
| 接口 | 方法 | 说明 | 认证 |
|------|------|------|------|
| GET /api/cases | GET | 获取案例列表 | 否 |
| GET /api/cases/hot | GET | 获取热门案例 | 否 |
| GET /api/cases/:id | GET | 获取案例详情 | 否 |
| POST /api/cases/:id/view | POST | 增加浏览量 | 否 |
| POST /api/cases/:id/like | POST | 点赞 | 是 |
### 3.3 用户接口
| 接口 | 方法 | 说明 | 认证 |
|------|------|------|------|
| GET /api/user/info | GET | 获取用户信息 | 是 |
| PUT /api/user/info | PUT | 更新用户信息 | 是 |
| GET /api/user/favorites | GET | 获取收藏列表 | 是 |
| POST /api/user/favorites | POST | 添加收藏 | 是 |
| DELETE /api/user/favorites/:caseId | DELETE | 取消收藏 | 是 |
### 3.4 预约接口
| 接口 | 方法 | 说明 | 认证 |
|------|------|------|------|
| POST /api/booking | POST | 提交预约 | 否 |
| GET /api/user/bookings | GET | 获取我的预约 | 是 |
| GET /api/user/bookings/:id | GET | 获取预约详情 | 是 |
| PUT /api/user/bookings/:id/cancel | PUT | 取消预约 | 是 |
### 3.5 文件接口
| 接口 | 方法 | 说明 | 认证 |
|------|------|------|------|
| POST /api/upload/image | POST | 上传图片 | 是 |
## 四、接口详细定义
### 4.1 微信登录
```
POST /api/auth/wxlogin
Request:
{
"code": "微信登录code",
"userInfo": {
"nickName": "昵称",
"avatarUrl": "头像"
}
}
Response:
{
"code": 0,
"message": "success",
"data": {
"token": "jwt_token_xxx",
"userInfo": {
"id": "1",
"nickName": "昵称",
"avatar": "头像URL",
"phone": ""
}
}
}
```
### 4.2 获取案例列表
```
GET /api/cases?category=leather&page=1&pageSize=10&keyword=
Request Params:
- category: 分类code可选
- page: 页码默认1
- pageSize: 每页数量默认10
- keyword: 搜索关键词,可选
Response:
{
"code": 0,
"message": "success",
"data": {
"list": [
{
"id": "1",
"title": "欧式真皮沙发翻新",
"category": "leather",
"categoryName": "皮沙发",
"coverImage": "https://xxx/cover.jpg",
"material": "进口头层牛皮",
"duration": "7天",
"price": "¥3800",
"views": 1256,
"likes": 89,
"createTime": "2026-01-15"
}
],
"total": 100,
"page": 1,
"pageSize": 10
}
}
```
### 4.3 获取案例详情
```
GET /api/cases/:id
Response:
{
"code": 0,
"message": "success",
"data": {
"id": "1",
"title": "欧式真皮沙发翻新",
"category": "leather",
"categoryName": "皮沙发",
"beforeImages": ["https://xxx/before1.jpg"],
"afterImages": ["https://xxx/after1.jpg"],
"description": "案例描述...",
"material": "进口头层牛皮",
"duration": "7天",
"price": "¥3800",
"views": 1256,
"likes": 89,
"createTime": "2026-01-15",
"isFavorite": false
}
}
```
### 4.4 提交预约
```
POST /api/booking
Request:
{
"userName": "张三",
"phone": "13800138000",
"address": "上海市浦东新区xxx",
"sofaType": "leather",
"problem": "皮面开裂、褪色",
"images": ["https://xxx/1.jpg", "https://xxx/2.jpg"]
}
Response:
{
"code": 0,
"message": "success",
"data": {
"bookingId": "BK20260126001",
"status": "pending",
"message": "预约成功,我们会尽快与您联系"
}
}
```
## 五、管理后台功能
### 5.1 功能模块
| 模块 | 功能 |
|------|------|
| 仪表盘 | 数据概览、预约统计、案例统计 |
| 案例管理 | 案例列表、新增/编辑/删除、上下架 |
| 分类管理 | 分类列表、新增/编辑/删除 |
| 预约管理 | 预约列表、状态更新、指派处理 |
| 用户管理 | 用户列表、状态管理 |
| 轮播图管理 | 轮播图列表、新增/编辑/删除 |
| 系统设置 | 公司信息、联系方式、服务流程 |
### 5.2 角色权限
| 角色 | 权限 |
|------|------|
| 超级管理员 | 所有权限 |
| 运营人员 | 案例管理、轮播图、系统设置 |
| 客服人员 | 预约管理、用户管理 |
## 六、部署架构
```
┌─────────────┐
│ Nginx │
│ (反向代理) │
└──────┬──────┘
┌───────────────┼───────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ 小程序API │ │ 管理后台 │ │ 静态资源 │
│ Server │ │ Server │ │ CDN │
└──────┬──────┘ └──────┬──────┘ └─────────────┘
│ │
└───────┬───────┘
┌───────▼───────┐
│ MySQL │
│ Redis │
└───────────────┘
```
## 七、开发计划
| 阶段 | 时间 | 任务 |
|------|------|------|
| 第一阶段 | 3天 | 数据库设计、项目搭建、基础接口 |
| 第二阶段 | 3天 | 案例模块、分类模块、轮播图 |
| 第三阶段 | 2天 | 用户模块、微信登录、收藏功能 |
| 第四阶段 | 2天 | 预约模块、文件上传 |
| 第五阶段 | 3天 | 管理后台开发 |
| 第六阶段 | 2天 | 联调测试、部署上线 |
**总计约15个工作日**
---
准备好开始后端开发时告诉我你选择的技术栈Node.js/Python/Java我将为你生成完整的后端代码。

View File

@@ -0,0 +1,82 @@
# 后端API对接完成报告
## 🎯 对接状态
**已成功对接后端API服务器**
- 后端运行在http://localhost:3000
- API文档地址http://localhost:3000/docs
- API基础路径http://localhost:3000/api
## 🔧 已完成的配置修改
### 1. 关闭Mock数据模式
- 文件:`utils/config.uts`
- 修改:`useMock = false`
- 说明已切换到真实后端API
### 2. 更新API接口调用
- 文件:`api/index.uts`
- 修改内容:
-`getCaseList()` - 适配后端分页参数page/limit
-`getHotCases()` - 使用案例列表API获取前4条数据
-`getBanners()` - 临时使用固定数据后端暂无此API
-`getServiceProcess()` - 改用服务列表API
### 3. 适配数据格式
- 文件:`pages/cases/list.uvue`, `pages/index/index.uvue`
- 修改:适应后端返回的数据结构
- 后端格式:`{ items: [], total: 0, page: 1, limit: 10 }`
- 前端适配:正确解析 `items` 字段
## 📊 API端点映射
| 功能 | 前端调用 | 后端端点 | 状态 |
|------|----------|----------|------|
| 案例列表 | `getCaseList()` | `GET /api/cases` | ✅ 已对接 |
| 案例详情 | `getCaseDetail(id)` | `GET /api/cases/:id` | ✅ 已对接 |
| 服务列表 | `getActiveServices()` | `GET /api/services/active` | ✅ 已对接 |
| 轮播图 | `getBanners()` | - | ⚠️ 临时使用固定数据 |
| 用户相关 | - | `GET /api/users/*` | ✅ 后端已准备 |
| 预约管理 | `submitBooking()` | `POST /api/booking` | 🔄 需要确认端点 |
## 🧪 测试验证
### 已验证的API
-`GET /api/cases` - 返回空列表(正常,暂无数据)
-`GET /api/services` - 返回空列表(正常,暂无数据)
- ✅ API文档页面 - 可正常访问
### 临时解决方案
1. **轮播图**使用硬编码数据后续可添加后端API
2. **空数据处理**:前端已正确处理空列表情况
## 📝 下一步工作
### 推荐优先级
1. **高优先级**
- 为后端添加示例数据(案例、服务)
- 验证预约提交API端点
- 添加轮播图管理API
2. **中优先级**
- 添加用户认证功能测试
- 完善错误处理机制
- 添加图片上传功能
3. **低优先级**
- API响应缓存机制
- 离线数据支持
## 🚀 启动应用测试
现在您可以:
1. 确保后端服务运行在 `http://localhost:3000`
2. 启动前端应用uni-app
3. 查看控制台API调用日志
4. 验证数据正确显示(目前为空列表)
## 📞 技术支持
如遇到问题,请检查:
- 后端服务是否正常运行
- 网络连接是否正常
- 控制台是否有错误信息

20
前端/index.html Normal file
View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main"></script>
</body>
</html>

9
前端/main.uts Normal file
View File

@@ -0,0 +1,9 @@
import App from './App.uvue'
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
}
}

64
前端/manifest.json Normal file
View File

@@ -0,0 +1,64 @@
{
"name": "优艺家沙发翻新",
"appid": "",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",
"uni-app-x": {},
/* */
"quickapp": {},
/* */
"mp-weixin": {
"appid": "wx89f1cd89fbc55f54",
"setting": {
"urlCheck": false
},
"usingComponents": true
},
"mp-alipay": {
"usingComponents": true
},
"mp-baidu": {
"usingComponents": true
},
"mp-toutiao": {
"usingComponents": true
},
"uniStatistics": {
"enable": false
},
"vueVersion": "3",
"app": {
"distribute": {
"icons": {
"android": {
"hdpi": "",
"xhdpi": "",
"xxhdpi": "",
"xxxhdpi": ""
}
}
}
},
"app-android": {
"distribute": {
"modules": {},
"icons": {
"hdpi": "",
"xhdpi": "",
"xxhdpi": "",
"xxxhdpi": ""
},
"splashScreens": {
"default": {}
}
}
},
"app-ios": {
"distribute": {
"modules": {},
"icons": {},
"splashScreens": {}
}
}
}

87
前端/pages.json Normal file
View File

@@ -0,0 +1,87 @@
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "优艺家沙发翻新",
"navigationStyle": "custom"
}
},
{
"path": "pages/cases/list",
"style": {
"navigationBarTitleText": "案例展示"
}
},
{
"path": "pages/cases/detail",
"style": {
"navigationBarTitleText": "案例详情",
"navigationBarBackgroundColor": "#ffffff"
}
},
{
"path": "pages/service/index",
"style": {
"navigationBarTitleText": "服务介绍"
}
},
{
"path": "pages/about/index",
"style": {
"navigationBarTitleText": "关于我们"
}
},
{
"path": "pages/booking/index",
"style": {
"navigationBarTitleText": "预约咨询"
}
},
{
"path": "pages/user/index",
"style": {
"navigationBarTitleText": "我的"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "优艺家沙发翻新",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#f5f5f5"
},
"tabBar": {
"color": "#999999",
"selectedColor": "#D4A574",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "static/icons/home.png",
"selectedIconPath": "static/icons/home-active.png",
"text": "首页"
},
{
"pagePath": "pages/cases/list",
"iconPath": "static/icons/cases.png",
"selectedIconPath": "static/icons/cases-active.png",
"text": "案例"
},
{
"pagePath": "pages/service/index",
"iconPath": "static/icons/service.png",
"selectedIconPath": "static/icons/service-active.png",
"text": "服务"
},
{
"pagePath": "pages/user/index",
"iconPath": "static/icons/user.png",
"selectedIconPath": "static/icons/user-active.png",
"text": "我的"
}
]
},
"uniIdRouter": {}
}

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

48
前端/static/README.md Normal file
View File

@@ -0,0 +1,48 @@
# 静态资源说明
## 目录结构
```
static/
├── icons/ # 图标文件
│ ├── home.svg # 首页图标(未选中)
│ ├── home-active.svg # 首页图标(选中)
│ ├── cases.svg # 案例图标(未选中)
│ ├── cases-active.svg # 案例图标(选中)
│ ├── service.svg # 服务图标(未选中)
│ ├── service-active.svg # 服务图标(选中)
│ ├── user.svg # 我的图标(未选中)
│ ├── user-active.svg # 我的图标(选中)
│ └── README.md # 图标转换说明
├── images/ # 图片资源
│ ├── default-avatar.svg # 默认头像
│ └── logo.svg # 公司Logo
└── mock/ # Mock数据图片
├── banner1.svg # 轮播图1
├── banner2.svg # 轮播图2
├── banner3.svg # 轮播图3
├── case1-before.svg # 案例1翻新前
├── case1-after.svg # 案例1翻新后
└── ... # 更多案例图片
```
## 重要说明
### TabBar 图标
微信小程序 TabBar **只支持 PNG 格式**,请参考 `icons/README.md` 将 SVG 转换为 PNG。
### 图片尺寸规范
1. **轮播图**:建议尺寸 750x320 像素
2. **案例图片**:建议尺寸 750x500 像素
3. **图标**:建议使用 PNG 格式,支持透明背景
4. **颜色规范**
- 主色:#D4A574
- 选中色:#D4A574
- 未选中色:#999999
## 注意事项
- 图片需要进行压缩优化,减小文件体积
- 图标建议使用纯色,便于主题切换
- 正式上线前需替换 Mock 图片为真实图片

View File

@@ -0,0 +1,54 @@
# 图标转换说明
## 问题
微信小程序 TabBar 图标只支持 PNG 格式,不支持 SVG。
## 解决方案
### 方法一:在线转换(推荐)
1. 访问 https://svgtopng.com/ 或 https://cloudconvert.com/svg-to-png
2. 上传 SVG 文件
3. 设置输出尺寸为 81x81 像素
4. 下载 PNG 文件并替换到 static/icons/ 目录
### 方法二:使用设计工具
1. 使用 Figma / Sketch / PS 打开 SVG
2. 导出为 81x81 像素的 PNG
3. 保存到 static/icons/ 目录
### 方法三:使用命令行工具
安装 sharp 或 imagemagick
```bash
# 使用 Node.js sharp
npm install sharp
node convert-icons.js
# 或使用 ImageMagick
convert home.svg -resize 81x81 home.png
```
## 需要转换的文件
| SVG 文件 | 输出 PNG | 尺寸 |
|----------|----------|------|
| home.svg | home.png | 81x81 |
| home-active.svg | home-active.png | 81x81 |
| cases.svg | cases.png | 81x81 |
| cases-active.svg | cases-active.png | 81x81 |
| service.svg | service.png | 81x81 |
| service-active.svg | service-active.png | 81x81 |
| user.svg | user.png | 81x81 |
| user-active.svg | user-active.png | 81x81 |
## 配色参考
- 未选中颜色:#999999
- 选中颜色:#D4A574(主题色)
## 图标设计规范
1. 图标尺寸81x81 像素
2. 安全区域:边距 10 像素
3. 图标风格:线性或填充图标
4. 背景:透明

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<circle cx="100" cy="100" r="100" fill="#E8E8E8"/>
<circle cx="100" cy="75" r="35" fill="#BDBDBD"/>
<ellipse cx="100" cy="155" rx="50" ry="35" fill="#BDBDBD"/>
</svg>

After

Width:  |  Height:  |  Size: 261 B

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<defs>
<linearGradient id="logoGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#D4A574"/>
<stop offset="100%" style="stop-color:#B8895A"/>
</linearGradient>
</defs>
<circle cx="100" cy="100" r="95" fill="url(#logoGradient)"/>
<text x="100" y="115" text-anchor="middle" font-size="48" font-weight="bold" fill="white" font-family="Arial">优艺家</text>
</svg>

After

Width:  |  Height:  |  Size: 511 B

BIN
前端/static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 320" width="750" height="320">
<defs>
<linearGradient id="bannerGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#D4A574"/>
<stop offset="100%" style="stop-color:#B8895A"/>
</linearGradient>
</defs>
<rect width="750" height="320" fill="url(#bannerGradient)"/>
<text x="375" y="140" text-anchor="middle" font-size="48" font-weight="bold" fill="white" font-family="Arial">专业沙发翻新服务</text>
<text x="375" y="200" text-anchor="middle" font-size="28" fill="rgba(255,255,255,0.8)" font-family="Arial">让旧沙发焕发新生</text>
</svg>

After

Width:  |  Height:  |  Size: 669 B

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 320" width="750" height="320">
<defs>
<linearGradient id="bannerGradient2" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#67C23A"/>
<stop offset="100%" style="stop-color:#529b2e"/>
</linearGradient>
</defs>
<rect width="750" height="320" fill="url(#bannerGradient2)"/>
<text x="375" y="140" text-anchor="middle" font-size="48" font-weight="bold" fill="white" font-family="Arial">十年品质保证</text>
<text x="375" y="200" text-anchor="middle" font-size="28" fill="rgba(255,255,255,0.8)" font-family="Arial">质保期内免费维护</text>
</svg>

After

Width:  |  Height:  |  Size: 665 B

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 320" width="750" height="320">
<defs>
<linearGradient id="bannerGradient3" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#409EFF"/>
<stop offset="100%" style="stop-color:#337ecc"/>
</linearGradient>
</defs>
<rect width="750" height="320" fill="url(#bannerGradient3)"/>
<text x="375" y="140" text-anchor="middle" font-size="48" font-weight="bold" fill="white" font-family="Arial">免费上门评估</text>
<text x="375" y="200" text-anchor="middle" font-size="28" fill="rgba(255,255,255,0.8)" font-family="Arial">专业师傅为您量身定制方案</text>
</svg>

After

Width:  |  Height:  |  Size: 677 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 500" width="750" height="500">
<rect width="750" height="500" fill="#D4A574"/>
<text x="375" y="230" text-anchor="middle" font-size="36" fill="rgba(255,255,255,0.8)" font-family="Arial">翻新后</text>
<text x="375" y="280" text-anchor="middle" font-size="24" fill="rgba(255,255,255,0.6)" font-family="Arial">欧式真皮沙发</text>
</svg>

After

Width:  |  Height:  |  Size: 406 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 500" width="750" height="500">
<rect width="750" height="500" fill="#8B7355"/>
<text x="375" y="230" text-anchor="middle" font-size="36" fill="rgba(255,255,255,0.6)" font-family="Arial">翻新前</text>
<text x="375" y="280" text-anchor="middle" font-size="24" fill="rgba(255,255,255,0.4)" font-family="Arial">欧式真皮沙发</text>
</svg>

After

Width:  |  Height:  |  Size: 406 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 500" width="750" height="500">
<rect width="750" height="500" fill="#67C23A"/>
<text x="375" y="230" text-anchor="middle" font-size="36" fill="rgba(255,255,255,0.8)" font-family="Arial">翻新后</text>
<text x="375" y="280" text-anchor="middle" font-size="24" fill="rgba(255,255,255,0.6)" font-family="Arial">现代简约布艺沙发</text>
</svg>

After

Width:  |  Height:  |  Size: 412 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 500" width="750" height="500">
<rect width="750" height="500" fill="#6B8E6B"/>
<text x="375" y="230" text-anchor="middle" font-size="36" fill="rgba(255,255,255,0.6)" font-family="Arial">翻新前</text>
<text x="375" y="280" text-anchor="middle" font-size="24" fill="rgba(255,255,255,0.4)" font-family="Arial">现代简约布艺沙发</text>
</svg>

After

Width:  |  Height:  |  Size: 412 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 500" width="750" height="500">
<rect width="750" height="500" fill="#F5F5DC"/>
<text x="375" y="230" text-anchor="middle" font-size="36" fill="rgba(0,0,0,0.5)" font-family="Arial">翻新后</text>
<text x="375" y="280" text-anchor="middle" font-size="24" fill="rgba(0,0,0,0.3)" font-family="Arial">美式复古沙发</text>
</svg>

After

Width:  |  Height:  |  Size: 394 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 500" width="750" height="500">
<rect width="750" height="500" fill="#5D4037"/>
<text x="375" y="230" text-anchor="middle" font-size="36" fill="rgba(255,255,255,0.6)" font-family="Arial">翻新前</text>
<text x="375" y="280" text-anchor="middle" font-size="24" fill="rgba(255,255,255,0.4)" font-family="Arial">美式复古沙发</text>
</svg>

After

Width:  |  Height:  |  Size: 406 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 500" width="750" height="500">
<rect width="750" height="500" fill="#607D8B"/>
<text x="375" y="230" text-anchor="middle" font-size="36" fill="rgba(255,255,255,0.8)" font-family="Arial">翻新后</text>
<text x="375" y="280" text-anchor="middle" font-size="24" fill="rgba(255,255,255,0.6)" font-family="Arial">功能沙发</text>
</svg>

After

Width:  |  Height:  |  Size: 400 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 500" width="750" height="500">
<rect width="750" height="500" fill="#455A64"/>
<text x="375" y="230" text-anchor="middle" font-size="36" fill="rgba(255,255,255,0.6)" font-family="Arial">翻新前</text>
<text x="375" y="280" text-anchor="middle" font-size="24" fill="rgba(255,255,255,0.4)" font-family="Arial">功能沙发</text>
</svg>

After

Width:  |  Height:  |  Size: 400 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 500" width="750" height="500">
<rect width="750" height="500" fill="#A1887F"/>
<text x="375" y="230" text-anchor="middle" font-size="36" fill="rgba(255,255,255,0.8)" font-family="Arial">翻新后</text>
<text x="375" y="280" text-anchor="middle" font-size="24" fill="rgba(255,255,255,0.6)" font-family="Arial">中式红木沙发垫</text>
</svg>

After

Width:  |  Height:  |  Size: 409 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 500" width="750" height="500">
<rect width="750" height="500" fill="#8D6E63"/>
<text x="375" y="230" text-anchor="middle" font-size="36" fill="rgba(255,255,255,0.6)" font-family="Arial">翻新前</text>
<text x="375" y="280" text-anchor="middle" font-size="24" fill="rgba(255,255,255,0.4)" font-family="Arial">中式红木沙发垫</text>
</svg>

After

Width:  |  Height:  |  Size: 409 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 500" width="750" height="500">
<rect width="750" height="500" fill="#263238"/>
<text x="375" y="230" text-anchor="middle" font-size="36" fill="rgba(255,255,255,0.8)" font-family="Arial">翻新后</text>
<text x="375" y="280" text-anchor="middle" font-size="24" fill="rgba(255,255,255,0.6)" font-family="Arial">办公室皮沙发组合</text>
</svg>

After

Width:  |  Height:  |  Size: 412 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 500" width="750" height="500">
<rect width="750" height="500" fill="#37474F"/>
<text x="375" y="230" text-anchor="middle" font-size="36" fill="rgba(255,255,255,0.6)" font-family="Arial">翻新前</text>
<text x="375" y="280" text-anchor="middle" font-size="24" fill="rgba(255,255,255,0.4)" font-family="Arial">办公室皮沙发组合</text>
</svg>

After

Width:  |  Height:  |  Size: 412 B

110
前端/uni.scss Normal file
View File

@@ -0,0 +1,110 @@
/**
* 优艺家沙发翻新 - 全局样式变量
*/
/* ========== 项目主题色 ========== */
$primary-color: #D4A574;
$primary-light: #E8C9A8;
$primary-dark: #B8895A;
/* 辅助色 */
$success-color: #67C23A;
$warning-color: #E6A23C;
$danger-color: #F56C6C;
$info-color: #909399;
/* 文字颜色 */
$text-primary: #333333;
$text-regular: #606266;
$text-secondary: #909399;
$text-placeholder: #C0C4CC;
/* 背景色 */
$bg-color: #f5f5f5;
$bg-white: #ffffff;
$bg-gray: #fafafa;
/* 边框颜色 */
$border-color: #EBEEF5;
$border-light: #F2F6FC;
/* 间距 */
$spacing-xs: 8rpx;
$spacing-sm: 16rpx;
$spacing-md: 24rpx;
$spacing-lg: 32rpx;
$spacing-xl: 48rpx;
/* 圆角 */
$radius-sm: 8rpx;
$radius-md: 16rpx;
$radius-lg: 24rpx;
$radius-round: 999rpx;
/* 阴影 */
$shadow-sm: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
$shadow-md: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
$shadow-lg: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
/* ========== uni-app 内置变量 ========== */
/* 行为相关颜色 */
$uni-color-primary: #D4A574;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color:#333;//基本色
$uni-text-color-inverse:#fff;//反色
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
/* 边框颜色 */
$uni-border-color:#c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm:12px;
$uni-font-size-base:14px;
$uni-font-size-lg:16px;
/* 图片尺寸 */
$uni-img-size-sm:20px;
$uni-img-size-base:26px;
$uni-img-size-lg:40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色
$uni-font-size-title:20px;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:15px;

View File

@@ -0,0 +1 @@
{"code":"import { defineComponent as _defineComponent } from 'vue';\nimport { o as _o, gei as _gei, sei as _sei, e as _e } from \"vue\";\nexport default /*#__PURE__*/ _defineComponent({\n __name: 'before-after',\n props: {\n beforeImage: {},\n afterImage: {},\n showTitle: { type: Boolean }\n },\n setup(__props) {\n const props = __props;\n // 预览图片\n const previewImage = (url, type) => {\n const urls = type == 'before' ? [props.beforeImage] : [props.afterImage];\n uni.previewImage({\n current: url,\n urls: urls\n });\n };\n return (_ctx, _cache) => {\n \"raw js\";\n const __returned__ = _e({\n a: _ctx.showTitle\n }, _ctx.showTitle ? {} : {}, {\n b: _ctx.beforeImage,\n c: _o($event => { return previewImage(_ctx.beforeImage, 'before'); }),\n d: _ctx.afterImage,\n e: _o($event => { return previewImage(_ctx.afterImage, 'after'); }),\n f: _sei(_gei(_ctx, ''), 'view')\n });\n return __returned__;\n };\n }\n});\n//# sourceMappingURL=D:/Project/Self/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/components/before-after/before-after.uvue?vue&type=script&setup=true&lang.uts.js.map","references":["D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts","D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts"],"uniExtApis":["uni.previewImage"],"map":"{\"version\":3,\"file\":\"before-after.uvue?vue&type=script&setup=true&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"before-after.uvue?vue&type=script&setup=true&lang.uts\"],\"names\":[],\"mappings\":\"AAAA,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,MAAM,KAAK,CAAA;AACzD,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,MAAM,KAAK,CAAA;AAGhE,eAAe,aAAa,CAAA,gBAAgB,CAAC;IAC3C,MAAM,EAAE,cAAc;IACtB,KAAK,EAAE;QACL,WAAW,EAAE,EAAE;QACf,UAAU,EAAE,EAAE;QACd,SAAS,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;KAC7B;IACD,KAAK,CAAC,OAAY;QAEnB,MAAM,KAAK,GAAG,OAAO,CAAA;QAErB,OAAO;QACP,MAAM,YAAY,GAAG,CAAC,GAAY,EAAE,IAAa;YAChD,MAAM,IAAI,GAAG,IAAI,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;YACxE,GAAG,CAAC,YAAY,CAAC;gBAChB,OAAO,EAAE,GAAG;gBACZ,IAAI,EAAE,IAAI;aACV,CAAC,CAAA;QACH,CAAC,CAAA;QAEF,OAAO,CAAC,IAAI,EAAE,MAAM;YAAO,QAAQ,CAAA;YACjC,MAAM,YAAY,GAAG,EAAE,CAAC;gBACxB,CAAC,EAAE,IAAI,CAAC,SAAS;aAClB,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC3B,CAAC,EAAE,IAAI,CAAC,WAAW;gBACnB,CAAC,EAAE,EAAE,CAAC,MAAM,MAAI,OAAA,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAxC,CAAwC,CAAC;gBACzD,CAAC,EAAE,IAAI,CAAC,UAAU;gBAClB,CAAC,EAAE,EAAE,CAAC,MAAM,MAAI,OAAA,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EAAtC,CAAsC,CAAC;gBACvD,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC;aAChC,CAAC,CAAA;YACA,OAAO,YAAY,CAAA;QACrB,CAAC,CAAA;IACD,CAAC;CAEA,CAAC,CAAA\"}"}

View File

@@ -0,0 +1 @@
{"code":"import {} from \"vue\";\nexport default defineComponent({\n onLaunch() {\n uni.__f__('log', 'at App.uvue:7', 'App Launch');\n },\n onShow() {\n uni.__f__('log', 'at App.uvue:10', 'App Show');\n },\n onHide() {\n uni.__f__('log', 'at App.uvue:13', 'App Hide');\n },\n onExit() {\n uni.__f__('log', 'at App.uvue:34', 'App Exit');\n },\n});\n//# sourceMappingURL=D:/Project/Self/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/App.uvue?vue&type=script&lang.uts.js.map","references":[],"uniExtApis":["uni.__f__"],"map":"{\"version\":3,\"file\":\"App.uvue?vue&type=script&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"App.uvue?vue&type=script&lang.uts\"],\"names\":[],\"mappings\":\";AAIC,+BAAe;IACd,QAAQ;QACP,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,eAAe,EAAC,YAAY,CAAC,CAAA;IAC9C,CAAC;IACD,MAAM;QACL,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC7C,CAAC;IACD,MAAM;QACL,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC7C,CAAC;IAmBD,MAAM;QACL,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC7C,CAAC;CACD,EAAA\"}"}

View File

@@ -0,0 +1 @@
{"code":"import { defineComponent as _defineComponent } from 'vue';\nimport { toDisplayString as _toDisplayString, t as _t, o as _o, gei as _gei, sei as _sei } from \"vue\";\nexport default /*#__PURE__*/ _defineComponent({\n __name: 'service-card',\n props: {\n id: {},\n name: {},\n icon: {}\n },\n emits: [\"click\"],\n setup(__props, _a) {\n var __emit = _a.emit;\n const props = __props;\n const emit = __emit;\n const handleClick = () => {\n emit('click', props.id);\n };\n return (_ctx, _cache) => {\n \"raw js\";\n const __returned__ = {\n a: _ctx.icon,\n b: _t(_ctx.name),\n c: _sei(_gei(_ctx, ''), 'view'),\n d: _o(handleClick)\n };\n return __returned__;\n };\n }\n});\n//# sourceMappingURL=D:/Project/Self/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/components/service-card/service-card.uvue?vue&type=script&setup=true&lang.uts.js.map","references":["D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts","D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts"],"uniExtApis":[],"map":"{\"version\":3,\"file\":\"service-card.uvue?vue&type=script&setup=true&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"service-card.uvue?vue&type=script&setup=true&lang.uts\"],\"names\":[],\"mappings\":\"AAAA,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,MAAM,KAAK,CAAA;AACzD,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,MAAM,KAAK,CAAA;AAGrG,eAAe,aAAa,CAAA,gBAAgB,CAAC;IAC3C,MAAM,EAAE,cAAc;IACtB,KAAK,EAAE;QACL,EAAE,EAAE,EAAE;QACN,IAAI,EAAE,EAAE;QACR,IAAI,EAAE,EAAE;KACT;IACD,KAAK,EAAE,CAAC,OAAO,CAAC;IAChB,KAAK,CAAC,OAAY,EAAE,EAAgB;YAAR,MAAM,UAAA;QAEnC,MAAM,KAAK,GAAG,OAAO,CAAA;QAErB,MAAM,IAAI,GAAG,MAAM,CAAA;QAEnB,MAAM,WAAW,GAAG;YACnB,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,CAAA;QACxB,CAAC,CAAA;QAEF,OAAO,CAAC,IAAI,EAAE,MAAM;YAAO,QAAQ,CAAA;YACjC,MAAM,YAAY,GAAG;gBACrB,CAAC,EAAE,IAAI,CAAC,IAAI;gBACZ,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;gBAChB,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC;gBAC/B,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC;aACnB,CAAA;YACC,OAAO,YAAY,CAAA;QACrB,CAAC,CAAA;IACD,CAAC;CAEA,CAAC,CAAA\"}"}

View File

@@ -0,0 +1 @@
{"code":"import { defineComponent as _defineComponent } from 'vue';\nimport { unref as _unref, o as _o, toDisplayString as _toDisplayString, t as _t, e as _e } from \"vue\";\nexport default /*#__PURE__*/ _defineComponent({\n __name: 'nav-bar',\n props: {\n title: {},\n showBack: { type: Boolean },\n titleColor: {},\n bgColor: {}\n },\n setup(__props) {\n const props = __props;\n // 状态栏高度\n const statusBarHeight = ref(20);\n // 导航栏高度\n const navBarHeight = ref(44);\n onMounted(() => {\n const sysInfo = uni.getSystemInfoSync();\n statusBarHeight.value = sysInfo.statusBarHeight;\n const menuButtonInfo = uni.getMenuButtonBoundingClientRect();\n navBarHeight.value = (menuButtonInfo.top - sysInfo.statusBarHeight) * 2 + menuButtonInfo.height;\n });\n const handleBack = () => {\n uni.navigateBack(new UTSJSONObject({\n fail: () => {\n uni.switchTab({\n url: '/pages/index/index'\n });\n }\n }));\n };\n return (_ctx, _cache) => {\n \"raw js\";\n const __returned__ = _e({\n a: _ctx.showBack\n }, _ctx.showBack ? {\n b: _o(handleBack)\n } : {}, {\n c: _t(_ctx.title),\n d: _ctx.titleColor,\n e: _unref(navBarHeight) + 'px',\n f: _unref(statusBarHeight) + 'px',\n g: _unref(statusBarHeight) + _unref(navBarHeight) + 'px'\n });\n return __returned__;\n };\n }\n});\n//# sourceMappingURL=D:/Project/Self/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/components/nav-bar/nav-bar.uvue?vue&type=script&setup=true&lang.uts.js.map","references":["D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts","D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts"],"uniExtApis":["uni.getSystemInfoSync","uni.getMenuButtonBoundingClientRect","uni.switchTab","uni.navigateBack"],"map":"{\"version\":3,\"file\":\"nav-bar.uvue?vue&type=script&setup=true&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"nav-bar.uvue?vue&type=script&setup=true&lang.uts\"],\"names\":[],\"mappings\":\"AAAA,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,MAAM,KAAK,CAAA;AACzD,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,eAAe,IAAI,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,MAAM,KAAK,CAAA;AAGrG,eAAe,aAAa,CAAA,gBAAgB,CAAC;IAC3C,MAAM,EAAE,SAAS;IACjB,KAAK,EAAE;QACL,KAAK,EAAE,EAAE;QACT,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;QAC3B,UAAU,EAAE,EAAE;QACd,OAAO,EAAE,EAAE;KACZ;IACD,KAAK,CAAC,OAAY;QAEnB,MAAM,KAAK,GAAG,OAAO,CAAA;QAErB,QAAQ;QACR,MAAM,eAAe,GAAG,GAAG,CAAC,EAAE,CAAC,CAAA;QAC/B,QAAQ;QACR,MAAM,YAAY,GAAG,GAAG,CAAC,EAAE,CAAC,CAAA;QAE5B,SAAS,CAAC;YACT,MAAM,OAAO,GAAG,GAAG,CAAC,iBAAiB,EAAE,CAAA;YACvC,eAAe,CAAC,KAAK,GAAG,OAAO,CAAC,eAAe,CAAA;YAE/C,MAAM,cAAc,GAAG,GAAG,CAAC,+BAA+B,EAAE,CAAA;YAC5D,YAAY,CAAC,KAAK,GAAG,CAAC,cAAc,CAAC,GAAG,GAAG,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC,MAAM,CAAA;QAEhG,CAAC,CAAC,CAAA;QAEF,MAAM,UAAU,GAAG;YAClB,GAAG,CAAC,YAAY,mBAAC;gBAChB,IAAI,EAAE;oBACL,GAAG,CAAC,SAAS,CAAC;wBACb,GAAG,EAAE,oBAAoB;qBACzB,CAAC,CAAA;gBACH,CAAC;aACD,EAAC,CAAA;QACH,CAAC,CAAA;QAEF,OAAO,CAAC,IAAI,EAAE,MAAM;YAAO,QAAQ,CAAA;YACjC,MAAM,YAAY,GAAG,EAAE,CAAC;gBACxB,CAAC,EAAE,IAAI,CAAC,QAAQ;aACjB,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACjB,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC;aAClB,CAAC,CAAC,CAAC,EAAE,EAAE;gBACN,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;gBACjB,CAAC,EAAE,IAAI,CAAC,UAAU;gBAClB,CAAC,EAAE,MAAM,CAAC,YAAY,CAAC,GAAG,IAAI;gBAC9B,CAAC,EAAE,MAAM,CAAC,eAAe,CAAC,GAAG,IAAI;gBACjC,CAAC,EAAE,MAAM,CAAC,eAAe,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,IAAI;aACzD,CAAC,CAAA;YACA,OAAO,YAAY,CAAA;QACrB,CAAC,CAAA;IACD,CAAC;CAEA,CAAC,CAAA\"}"}

View File

@@ -0,0 +1 @@
{"code":"import { defineComponent as _defineComponent } from 'vue';\nimport { toDisplayString as _toDisplayString, t as _t, o as _o, gei as _gei, sei as _sei } from \"vue\";\nclass CaseItem extends UTS.UTSType {\n static get$UTSMetadata$() {\n return {\n kind: 2,\n get fields() {\n return {\n id: { type: String, optional: false },\n title: { type: String, optional: false },\n category: { type: String, optional: false },\n categoryName: { type: String, optional: false },\n coverImage: { type: String, optional: false },\n material: { type: String, optional: false },\n duration: { type: String, optional: false },\n price: { type: String, optional: false },\n views: { type: Number, optional: false },\n likes: { type: Number, optional: false }\n };\n },\n name: \"CaseItem\"\n };\n }\n constructor(options, metadata = CaseItem.get$UTSMetadata$(), isJSONParse = false) {\n super();\n this.__props__ = UTS.UTSType.initProps(options, metadata, isJSONParse);\n this.id = this.__props__.id;\n this.title = this.__props__.title;\n this.category = this.__props__.category;\n this.categoryName = this.__props__.categoryName;\n this.coverImage = this.__props__.coverImage;\n this.material = this.__props__.material;\n this.duration = this.__props__.duration;\n this.price = this.__props__.price;\n this.views = this.__props__.views;\n this.likes = this.__props__.likes;\n delete this.__props__;\n }\n}\nexport default /*#__PURE__*/ _defineComponent({\n __name: 'case-card',\n props: {\n caseData: {}\n },\n emits: [\"click\"],\n setup(__props, _a) {\n var __emit = _a.emit;\n // 定义Props类型\n const props = __props;\n const emit = __emit;\n const handleClick = () => {\n emit('click', props.caseData.id);\n };\n return (_ctx, _cache) => {\n \"raw js\";\n const __returned__ = {\n a: _ctx.caseData.coverImage,\n b: _t(_ctx.caseData.categoryName),\n c: _t(_ctx.caseData.title),\n d: _t(_ctx.caseData.material),\n e: _t(_ctx.caseData.duration),\n f: _t(_ctx.caseData.price),\n g: _t(_ctx.caseData.views),\n h: _t(_ctx.caseData.likes),\n i: _sei(_gei(_ctx, ''), 'view'),\n j: _o(handleClick)\n };\n return __returned__;\n };\n }\n});\n//# sourceMappingURL=D:/Project/Self/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/components/case-card/case-card.uvue?vue&type=script&setup=true&lang.uts.js.map","references":["D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts","D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts"],"uniExtApis":[],"map":"{\"version\":3,\"file\":\"case-card.uvue?vue&type=script&setup=true&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"case-card.uvue?vue&type=script&setup=true&lang.uts\"],\"names\":[],\"mappings\":\"AAAA,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,MAAM,KAAK,CAAA;AACzD,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,MAAM,KAAK,CAAA;MAEhG,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcb,eAAe,aAAa,CAAA,gBAAgB,CAAC;IAC3C,MAAM,EAAE,WAAW;IACnB,KAAK,EAAE;QACL,QAAQ,EAAE,EAAE;KACb;IACD,KAAK,EAAE,CAAC,OAAO,CAAC;IAChB,KAAK,CAAC,OAAY,EAAE,EAAgB;YAAR,MAAM,UAAA;QAEnC,YAAY;QACZ,MAAM,KAAK,GAAG,OAAO,CAAA;QAErB,MAAM,IAAI,GAAG,MAAM,CAAA;QAEnB,MAAM,WAAW,GAAG;YACnB,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QACjC,CAAC,CAAA;QAEF,OAAO,CAAC,IAAI,EAAE,MAAM;YAAO,QAAQ,CAAA;YACjC,MAAM,YAAY,GAAG;gBACrB,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU;gBAC3B,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;gBACjC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAC1B,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAC7B,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAC7B,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAC1B,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAC1B,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAC1B,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC;gBAC/B,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC;aACnB,CAAA;YACC,OAAO,YAAY,CAAA;QACrB,CAAC,CAAA;IACD,CAAC;CAEA,CAAC,CAAA\"}"}

View File

@@ -0,0 +1 @@
{"code":"import { defineComponent as _defineComponent } from 'vue';\nimport { toDisplayString as _toDisplayString, t as _t, o as _o, gei as _gei, sei as _sei, e as _e } from \"vue\";\nexport default /*#__PURE__*/ _defineComponent({\n __name: 'section-header',\n props: {\n title: {},\n showMore: { type: Boolean }\n },\n emits: [\"more\"],\n setup(__props, _a) {\n var __emit = _a.emit;\n const props = __props;\n const emit = __emit;\n const handleMore = () => {\n emit('more');\n };\n return (_ctx, _cache) => {\n \"raw js\";\n const __returned__ = _e({\n a: _t(_ctx.title),\n b: _ctx.showMore\n }, _ctx.showMore ? {\n c: _o(handleMore)\n } : {}, {\n d: _sei(_gei(_ctx, ''), 'view')\n });\n return __returned__;\n };\n }\n});\n//# sourceMappingURL=D:/Project/Self/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/components/section-header/section-header.uvue?vue&type=script&setup=true&lang.uts.js.map","references":["D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts","D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts"],"uniExtApis":[],"map":"{\"version\":3,\"file\":\"section-header.uvue?vue&type=script&setup=true&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"section-header.uvue?vue&type=script&setup=true&lang.uts\"],\"names\":[],\"mappings\":\"AAAA,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,MAAM,KAAK,CAAA;AACzD,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,MAAM,KAAK,CAAA;AAG9G,eAAe,aAAa,CAAA,gBAAgB,CAAC;IAC3C,MAAM,EAAE,gBAAgB;IACxB,KAAK,EAAE;QACL,KAAK,EAAE,EAAE;QACT,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;KAC5B;IACD,KAAK,EAAE,CAAC,MAAM,CAAC;IACf,KAAK,CAAC,OAAY,EAAE,EAAgB;YAAR,MAAM,UAAA;QAEnC,MAAM,KAAK,GAAG,OAAO,CAAA;QAErB,MAAM,IAAI,GAAG,MAAM,CAAA;QAEnB,MAAM,UAAU,GAAG;YAClB,IAAI,CAAC,MAAM,CAAC,CAAA;QACb,CAAC,CAAA;QAEF,OAAO,CAAC,IAAI,EAAE,MAAM;YAAO,QAAQ,CAAA;YACjC,MAAM,YAAY,GAAG,EAAE,CAAC;gBACxB,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;gBACjB,CAAC,EAAE,IAAI,CAAC,QAAQ;aACjB,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACjB,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC;aAClB,CAAC,CAAC,CAAC,EAAE,EAAE;gBACN,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC;aAChC,CAAC,CAAA;YACA,OAAO,YAAY,CAAA;QACrB,CAAC,CAAA;IACD,CAAC;CAEA,CAAC,CAAA\"}"}

View File

@@ -0,0 +1 @@
{"code":"import { get, post } from \"../utils/request\";\n/**\n * 获取轮播图\n */\nexport const getBanners = () => {\n return get('/banners');\n};\n/**\n * 获取案例列表\n */\nexport const getCaseList = (params) => {\n return get('/cases', params);\n};\n/**\n * 获取案例详情\n */\nexport const getCaseDetail = (id) => {\n return get(`/cases/${id}`);\n};\n/**\n * 获取热门案例\n */\nexport const getHotCases = () => {\n return get('/cases/hot');\n};\n/**\n * 获取服务流程\n */\nexport const getServiceProcess = () => {\n return get('/service/process');\n};\n/**\n * 获取公司信息\n */\nexport const getCompanyInfo = () => {\n return get('/company/info');\n};\n/**\n * 提交预约\n */\nexport const submitBooking = (data) => {\n return post('/booking', data);\n};\n/**\n * 获取用户信息\n */\nexport const getUserInfo = () => {\n return get('/user/info');\n};\n/**\n * 获取用户收藏列表\n */\nexport const getFavorites = () => {\n return get('/user/favorites');\n};\n/**\n * 添加收藏\n */\nexport const addFavorite = (caseId) => {\n return post('/user/favorites', new UTSJSONObject({ caseId: caseId }));\n};\n/**\n * 取消收藏\n */\nexport const removeFavorite = (caseId) => {\n return post('/user/favorites/remove', new UTSJSONObject({ caseId: caseId }));\n};\n/**\n * 获取预约记录\n */\nexport const getBookingList = () => {\n return get('/user/bookings');\n};\n//# sourceMappingURL=D:/Project/Self/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/api/index.uts.js.map","references":[],"uniExtApis":[],"map":"{\"version\":3,\"file\":\"index.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"index.uts\"],\"names\":[],\"mappings\":\"OAGO,EAAE,GAAG,EAAE,IAAI,EAAE;AAEpB;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACzB,OAAO,GAAG,CAAC,UAAU,CAAC,CAAA;AACvB,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAsB;IACjD,OAAO,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;AAC7B,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAAW;IACxC,OAAO,GAAG,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;AAC3B,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IAC1B,OAAO,GAAG,CAAC,YAAY,CAAC,CAAA;AACzB,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAChC,OAAO,GAAG,CAAC,kBAAkB,CAAC,CAAA;AAC/B,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC7B,OAAO,GAAG,CAAC,eAAe,CAAC,CAAA;AAC5B,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAoB;IACjD,OAAO,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;AAC9B,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IAC1B,OAAO,GAAG,CAAC,YAAY,CAAC,CAAA;AACzB,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC3B,OAAO,GAAG,CAAC,iBAAiB,CAAC,CAAA;AAC9B,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAe;IAC1C,OAAO,IAAI,CAAC,iBAAiB,oBAAE,EAAE,MAAM,EAAE,MAAM,EAAmB,EAAC,CAAA;AACpE,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAe;IAC7C,OAAO,IAAI,CAAC,wBAAwB,oBAAE,EAAE,MAAM,EAAE,MAAM,EAAmB,EAAC,CAAA;AAC3E,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC7B,OAAO,GAAG,CAAC,gBAAgB,CAAC,CAAA;AAC7B,CAAC,CAAA\"}"}

View File

@@ -0,0 +1 @@
{"code":"import { defineComponent as _defineComponent } from 'vue';\nimport { o as _o, gei as _gei, sei as _sei, e as _e } from \"vue\";\nexport default /*#__PURE__*/ _defineComponent({\n __name: 'before-after',\n props: {\n beforeImage: {},\n afterImage: {},\n showTitle: { type: Boolean }\n },\n setup(__props) {\n const props = __props;\n // 预览图片\n const previewImage = (url, type) => {\n const urls = type == 'before' ? [props.beforeImage] : [props.afterImage];\n uni.previewImage({\n current: url,\n urls: urls\n });\n };\n return (_ctx, _cache) => {\n \"raw js\";\n const __returned__ = _e({\n a: _ctx.showTitle\n }, _ctx.showTitle ? {} : {}, {\n b: _ctx.beforeImage,\n c: _o($event => { return previewImage(_ctx.beforeImage, 'before'); }),\n d: _ctx.afterImage,\n e: _o($event => { return previewImage(_ctx.afterImage, 'after'); }),\n f: _sei(_gei(_ctx, ''), 'view')\n });\n return __returned__;\n };\n }\n});\n//# sourceMappingURL=D:/Project/Self/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/components/before-after/before-after.uvue?vue&type=script&setup=true&lang.uts.js.map","references":["D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts","D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts"],"uniExtApis":["uni.previewImage"],"map":"{\"version\":3,\"file\":\"before-after.uvue?vue&type=script&setup=true&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"before-after.uvue?vue&type=script&setup=true&lang.uts\"],\"names\":[],\"mappings\":\"AAAA,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,MAAM,KAAK,CAAA;AACzD,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,MAAM,KAAK,CAAA;AAGhE,eAAe,aAAa,CAAA,gBAAgB,CAAC;IAC3C,MAAM,EAAE,cAAc;IACtB,KAAK,EAAE;QACL,WAAW,EAAE,EAAE;QACf,UAAU,EAAE,EAAE;QACd,SAAS,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;KAC7B;IACD,KAAK,CAAC,OAAY;QAEnB,MAAM,KAAK,GAAG,OAAO,CAAA;QAErB,OAAO;QACP,MAAM,YAAY,GAAG,CAAC,GAAY,EAAE,IAAa;YAChD,MAAM,IAAI,GAAG,IAAI,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;YACxE,GAAG,CAAC,YAAY,CAAC;gBAChB,OAAO,EAAE,GAAG;gBACZ,IAAI,EAAE,IAAI;aACV,CAAC,CAAA;QACH,CAAC,CAAA;QAEF,OAAO,CAAC,IAAI,EAAE,MAAM;YAAO,QAAQ,CAAA;YACjC,MAAM,YAAY,GAAG,EAAE,CAAC;gBACxB,CAAC,EAAE,IAAI,CAAC,SAAS;aAClB,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC3B,CAAC,EAAE,IAAI,CAAC,WAAW;gBACnB,CAAC,EAAE,EAAE,CAAC,MAAM,MAAI,OAAA,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAxC,CAAwC,CAAC;gBACzD,CAAC,EAAE,IAAI,CAAC,UAAU;gBAClB,CAAC,EAAE,EAAE,CAAC,MAAM,MAAI,OAAA,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EAAtC,CAAsC,CAAC;gBACvD,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC;aAChC,CAAC,CAAA;YACA,OAAO,YAAY,CAAA;QACrB,CAAC,CAAA;IACD,CAAC;CAEA,CAAC,CAAA\"}"}

View File

@@ -0,0 +1 @@
{"code":"/**\n * 项目配置文件\n */\n// 环境配置\nexport const ENV = new UTSJSONObject({\n // 开发环境\n development: new UTSJSONObject({\n baseUrl: 'http://localhost:3000/api',\n imageBaseUrl: 'http://localhost:3000'\n }),\n // 生产环境\n production: new UTSJSONObject({\n baseUrl: 'https://api.youyijia.com/api',\n imageBaseUrl: 'https://api.youyijia.com'\n })\n}\n// 当前环境 - 切换为 'production' 上线\n);\n// 当前环境 - 切换为 'production' 上线\nexport const currentEnv = 'development';\n// 获取当前环境配置\nexport const getEnvConfig = () => {\n if (currentEnv == 'production') {\n return new UTSJSONObject({\n baseUrl: ENV.production.baseUrl,\n imageBaseUrl: ENV.production.imageBaseUrl\n });\n }\n return new UTSJSONObject({\n baseUrl: ENV.development.baseUrl,\n imageBaseUrl: ENV.development.imageBaseUrl\n });\n};\n// 是否使用Mock数据\nexport const useMock = true;\n// 分页配置\nexport const PAGE_SIZE = 10;\n// 图片上传配置\nexport const UPLOAD_CONFIG = new UTSJSONObject({\n maxSize: 5 * 1024 * 1024,\n maxCount: 9,\n accept: ['image/jpeg', 'image/png', 'image/gif']\n}\n// 缓存Key\n);\n// 缓存Key\nexport const STORAGE_KEYS = new UTSJSONObject({\n TOKEN: 'user_token',\n USER_INFO: 'user_info',\n FAVORITES: 'user_favorites',\n SEARCH_HISTORY: 'search_history'\n}\n// 沙发分类\n);\n// 沙发分类\nexport const SOFA_CATEGORIES = [\n new UTSJSONObject({ id: 'all', name: '全部' }),\n new UTSJSONObject({ id: 'leather', name: '皮沙发' }),\n new UTSJSONObject({ id: 'fabric', name: '布艺沙发' }),\n new UTSJSONObject({ id: 'functional', name: '功能沙发' }),\n new UTSJSONObject({ id: 'antique', name: '古典沙发' }),\n new UTSJSONObject({ id: 'office', name: '办公沙发' })\n];\n// 翻新服务类型\nexport const SERVICE_TYPES = [\n new UTSJSONObject({ id: 'repair', name: '局部修复', icon: '/static/icons/repair.png' }),\n new UTSJSONObject({ id: 'recolor', name: '改色翻新', icon: '/static/icons/recolor.png' }),\n new UTSJSONObject({ id: 'refurbish', name: '整体翻新', icon: '/static/icons/refurbish.png' }),\n new UTSJSONObject({ id: 'custom', name: '定制换皮', icon: '/static/icons/custom.png' })\n];\n//# sourceMappingURL=D:/Project/Self/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/utils/config.uts.js.map","references":[],"uniExtApis":[],"map":"{\"version\":3,\"file\":\"config.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"config.uts\"],\"names\":[],\"mappings\":\"AAAA;;GAEG;AAEH,OAAO;AACP,MAAM,CAAC,MAAM,GAAG,qBAAG;IAClB,OAAO;IACP,WAAW,oBAAE;QACZ,OAAO,EAAE,2BAA2B;QACpC,YAAY,EAAE,uBAAuB;KACrC,CAAA;IACD,OAAO;IACP,UAAU,oBAAE;QACX,OAAO,EAAE,8BAA8B;QACvC,YAAY,EAAE,0BAA0B;KACxC,CAAA;CACD;AAED,6BAA6B;CAF5B,CAAA;AAED,6BAA6B;AAC7B,MAAM,CAAC,MAAM,UAAU,GAAG,aAAa,CAAA;AAEvC,WAAW;AACX,MAAM,CAAC,MAAM,YAAY,GAAG;IAC3B,IAAI,UAAU,IAAI,YAAY,EAAE;QAC/B,yBAAO;YACN,OAAO,EAAE,GAAG,CAAC,UAAU,CAAC,OAAO;YAC/B,YAAY,EAAE,GAAG,CAAC,UAAU,CAAC,YAAY;SACxB,EAAA;KAClB;IACD,yBAAO;QACN,OAAO,EAAE,GAAG,CAAC,WAAW,CAAC,OAAO;QAChC,YAAY,EAAE,GAAG,CAAC,WAAW,CAAC,YAAY;KACzB,EAAA;AACnB,CAAC,CAAA;AAED,aAAa;AACb,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAA;AAE3B,OAAO;AACP,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAA;AAE3B,SAAS;AACT,MAAM,CAAC,MAAM,aAAa,qBAAG;IAC5B,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;IACxB,QAAQ,EAAE,CAAC;IACX,MAAM,EAAE,CAAC,YAAY,EAAE,WAAW,EAAE,WAAW,CAAC;CAChD;AAED,QAAQ;CAFP,CAAA;AAED,QAAQ;AACR,MAAM,CAAC,MAAM,YAAY,qBAAG;IAC3B,KAAK,EAAE,YAAY;IACnB,SAAS,EAAE,WAAW;IACtB,SAAS,EAAE,gBAAgB;IAC3B,cAAc,EAAE,gBAAgB;CAChC;AAED,OAAO;CAFN,CAAA;AAED,OAAO;AACP,MAAM,CAAC,MAAM,eAAe,GAAG;sBAC9B,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE;sBACzB,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE;sBAC9B,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE;sBAC9B,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE;sBAClC,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE;sBAC/B,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE;CAC9B,CAAA;AAED,SAAS;AACT,MAAM,CAAC,MAAM,aAAa,GAAG;sBAC5B,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,EAAE;sBAChE,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,2BAA2B,EAAE;sBAClE,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,6BAA6B,EAAE;sBACtE,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,EAAE;CAChE,CAAA\"}"}

View File

@@ -0,0 +1 @@
{"code":"import { defineComponent as _defineComponent } from 'vue';\nimport { toDisplayString as _toDisplayString, t as _t, o as _o, gei as _gei, sei as _sei } from \"vue\";\nexport default /*#__PURE__*/ _defineComponent({\n __name: 'service-card',\n props: {\n id: {},\n name: {},\n icon: {}\n },\n emits: [\"click\"],\n setup(__props, _a) {\n var __emit = _a.emit;\n const props = __props;\n const emit = __emit;\n const handleClick = () => {\n emit('click', props.id);\n };\n return (_ctx, _cache) => {\n \"raw js\";\n const __returned__ = {\n a: _ctx.icon,\n b: _t(_ctx.name),\n c: _sei(_gei(_ctx, ''), 'view'),\n d: _o(handleClick)\n };\n return __returned__;\n };\n }\n});\n//# sourceMappingURL=D:/Project/Self/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/components/service-card/service-card.uvue?vue&type=script&setup=true&lang.uts.js.map","references":["D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts","D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts"],"uniExtApis":[],"map":"{\"version\":3,\"file\":\"service-card.uvue?vue&type=script&setup=true&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"service-card.uvue?vue&type=script&setup=true&lang.uts\"],\"names\":[],\"mappings\":\"AAAA,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,MAAM,KAAK,CAAA;AACzD,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,MAAM,KAAK,CAAA;AAGrG,eAAe,aAAa,CAAA,gBAAgB,CAAC;IAC3C,MAAM,EAAE,cAAc;IACtB,KAAK,EAAE;QACL,EAAE,EAAE,EAAE;QACN,IAAI,EAAE,EAAE;QACR,IAAI,EAAE,EAAE;KACT;IACD,KAAK,EAAE,CAAC,OAAO,CAAC;IAChB,KAAK,CAAC,OAAY,EAAE,EAAgB;YAAR,MAAM,UAAA;QAEnC,MAAM,KAAK,GAAG,OAAO,CAAA;QAErB,MAAM,IAAI,GAAG,MAAM,CAAA;QAEnB,MAAM,WAAW,GAAG;YACnB,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,CAAA;QACxB,CAAC,CAAA;QAEF,OAAO,CAAC,IAAI,EAAE,MAAM;YAAO,QAAQ,CAAA;YACjC,MAAM,YAAY,GAAG;gBACrB,CAAC,EAAE,IAAI,CAAC,IAAI;gBACZ,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;gBAChB,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC;gBAC/B,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC;aACnB,CAAA;YACC,OAAO,YAAY,CAAA;QACrB,CAAC,CAAA;IACD,CAAC;CAEA,CAAC,CAAA\"}"}

View File

@@ -0,0 +1 @@
{"code":"import {} from \"vue\";\nexport default defineComponent({\n onLaunch() {\n uni.__f__('log', 'at App.uvue:7', 'App Launch');\n },\n onShow() {\n uni.__f__('log', 'at App.uvue:10', 'App Show');\n },\n onHide() {\n uni.__f__('log', 'at App.uvue:13', 'App Hide');\n },\n onExit() {\n uni.__f__('log', 'at App.uvue:34', 'App Exit');\n },\n});\n//# sourceMappingURL=D:/Project/Self/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/App.uvue?vue&type=script&lang.uts.js.map","references":[],"uniExtApis":["uni.__f__"],"map":"{\"version\":3,\"file\":\"App.uvue?vue&type=script&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"App.uvue?vue&type=script&lang.uts\"],\"names\":[],\"mappings\":\";AAIC,+BAAe;IACd,QAAQ;QACP,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,eAAe,EAAC,YAAY,CAAC,CAAA;IAC9C,CAAC;IACD,MAAM;QACL,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC7C,CAAC;IACD,MAAM;QACL,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC7C,CAAC;IAmBD,MAAM;QACL,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC7C,CAAC;CACD,EAAA\"}"}

View File

@@ -0,0 +1 @@
{"code":"import { defineComponent as _defineComponent } from 'vue';\nimport { unref as _unref, o as _o, toDisplayString as _toDisplayString, t as _t, e as _e } from \"vue\";\nexport default /*#__PURE__*/ _defineComponent({\n __name: 'nav-bar',\n props: {\n title: {},\n showBack: { type: Boolean },\n titleColor: {},\n bgColor: {}\n },\n setup(__props) {\n const props = __props;\n // 状态栏高度\n const statusBarHeight = ref(20);\n // 导航栏高度\n const navBarHeight = ref(44);\n onMounted(() => {\n const sysInfo = uni.getSystemInfoSync();\n statusBarHeight.value = sysInfo.statusBarHeight;\n const menuButtonInfo = uni.getMenuButtonBoundingClientRect();\n navBarHeight.value = (menuButtonInfo.top - sysInfo.statusBarHeight) * 2 + menuButtonInfo.height;\n });\n const handleBack = () => {\n uni.navigateBack(new UTSJSONObject({\n fail: () => {\n uni.switchTab({\n url: '/pages/index/index'\n });\n }\n }));\n };\n return (_ctx, _cache) => {\n \"raw js\";\n const __returned__ = _e({\n a: _ctx.showBack\n }, _ctx.showBack ? {\n b: _o(handleBack)\n } : {}, {\n c: _t(_ctx.title),\n d: _ctx.titleColor,\n e: _unref(navBarHeight) + 'px',\n f: _unref(statusBarHeight) + 'px',\n g: _unref(statusBarHeight) + _unref(navBarHeight) + 'px'\n });\n return __returned__;\n };\n }\n});\n//# sourceMappingURL=D:/Project/Self/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/components/nav-bar/nav-bar.uvue?vue&type=script&setup=true&lang.uts.js.map","references":["D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts","D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts"],"uniExtApis":["uni.getSystemInfoSync","uni.getMenuButtonBoundingClientRect","uni.switchTab","uni.navigateBack"],"map":"{\"version\":3,\"file\":\"nav-bar.uvue?vue&type=script&setup=true&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"nav-bar.uvue?vue&type=script&setup=true&lang.uts\"],\"names\":[],\"mappings\":\"AAAA,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,MAAM,KAAK,CAAA;AACzD,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,eAAe,IAAI,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,MAAM,KAAK,CAAA;AAGrG,eAAe,aAAa,CAAA,gBAAgB,CAAC;IAC3C,MAAM,EAAE,SAAS;IACjB,KAAK,EAAE;QACL,KAAK,EAAE,EAAE;QACT,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;QAC3B,UAAU,EAAE,EAAE;QACd,OAAO,EAAE,EAAE;KACZ;IACD,KAAK,CAAC,OAAY;QAEnB,MAAM,KAAK,GAAG,OAAO,CAAA;QAErB,QAAQ;QACR,MAAM,eAAe,GAAG,GAAG,CAAC,EAAE,CAAC,CAAA;QAC/B,QAAQ;QACR,MAAM,YAAY,GAAG,GAAG,CAAC,EAAE,CAAC,CAAA;QAE5B,SAAS,CAAC;YACT,MAAM,OAAO,GAAG,GAAG,CAAC,iBAAiB,EAAE,CAAA;YACvC,eAAe,CAAC,KAAK,GAAG,OAAO,CAAC,eAAe,CAAA;YAE/C,MAAM,cAAc,GAAG,GAAG,CAAC,+BAA+B,EAAE,CAAA;YAC5D,YAAY,CAAC,KAAK,GAAG,CAAC,cAAc,CAAC,GAAG,GAAG,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC,MAAM,CAAA;QAEhG,CAAC,CAAC,CAAA;QAEF,MAAM,UAAU,GAAG;YAClB,GAAG,CAAC,YAAY,mBAAC;gBAChB,IAAI,EAAE;oBACL,GAAG,CAAC,SAAS,CAAC;wBACb,GAAG,EAAE,oBAAoB;qBACzB,CAAC,CAAA;gBACH,CAAC;aACD,EAAC,CAAA;QACH,CAAC,CAAA;QAEF,OAAO,CAAC,IAAI,EAAE,MAAM;YAAO,QAAQ,CAAA;YACjC,MAAM,YAAY,GAAG,EAAE,CAAC;gBACxB,CAAC,EAAE,IAAI,CAAC,QAAQ;aACjB,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACjB,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC;aAClB,CAAC,CAAC,CAAC,EAAE,EAAE;gBACN,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;gBACjB,CAAC,EAAE,IAAI,CAAC,UAAU;gBAClB,CAAC,EAAE,MAAM,CAAC,YAAY,CAAC,GAAG,IAAI;gBAC9B,CAAC,EAAE,MAAM,CAAC,eAAe,CAAC,GAAG,IAAI;gBACjC,CAAC,EAAE,MAAM,CAAC,eAAe,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,IAAI;aACzD,CAAC,CAAA;YACA,OAAO,YAAY,CAAA;QACrB,CAAC,CAAA;IACD,CAAC;CAEA,CAAC,CAAA\"}"}

View File

@@ -0,0 +1 @@
{"code":"import { defineComponent as _defineComponent } from 'vue';\nimport { toDisplayString as _toDisplayString, t as _t, o as _o, gei as _gei, sei as _sei } from \"vue\";\nclass CaseItem extends UTS.UTSType {\n static get$UTSMetadata$() {\n return {\n kind: 2,\n get fields() {\n return {\n id: { type: String, optional: false },\n title: { type: String, optional: false },\n category: { type: String, optional: false },\n categoryName: { type: String, optional: false },\n coverImage: { type: String, optional: false },\n material: { type: String, optional: false },\n duration: { type: String, optional: false },\n price: { type: String, optional: false },\n views: { type: Number, optional: false },\n likes: { type: Number, optional: false }\n };\n },\n name: \"CaseItem\"\n };\n }\n constructor(options, metadata = CaseItem.get$UTSMetadata$(), isJSONParse = false) {\n super();\n this.__props__ = UTS.UTSType.initProps(options, metadata, isJSONParse);\n this.id = this.__props__.id;\n this.title = this.__props__.title;\n this.category = this.__props__.category;\n this.categoryName = this.__props__.categoryName;\n this.coverImage = this.__props__.coverImage;\n this.material = this.__props__.material;\n this.duration = this.__props__.duration;\n this.price = this.__props__.price;\n this.views = this.__props__.views;\n this.likes = this.__props__.likes;\n delete this.__props__;\n }\n}\nexport default /*#__PURE__*/ _defineComponent({\n __name: 'case-card',\n props: {\n caseData: {}\n },\n emits: [\"click\"],\n setup(__props, _a) {\n var __emit = _a.emit;\n // 定义Props类型\n const props = __props;\n const emit = __emit;\n const handleClick = () => {\n emit('click', props.caseData.id);\n };\n return (_ctx, _cache) => {\n \"raw js\";\n const __returned__ = {\n a: _ctx.caseData.coverImage,\n b: _t(_ctx.caseData.categoryName),\n c: _t(_ctx.caseData.title),\n d: _t(_ctx.caseData.material),\n e: _t(_ctx.caseData.duration),\n f: _t(_ctx.caseData.price),\n g: _t(_ctx.caseData.views),\n h: _t(_ctx.caseData.likes),\n i: _sei(_gei(_ctx, ''), 'view'),\n j: _o(handleClick)\n };\n return __returned__;\n };\n }\n});\n//# sourceMappingURL=D:/Project/Self/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/components/case-card/case-card.uvue?vue&type=script&setup=true&lang.uts.js.map","references":["D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts","D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts"],"uniExtApis":[],"map":"{\"version\":3,\"file\":\"case-card.uvue?vue&type=script&setup=true&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"case-card.uvue?vue&type=script&setup=true&lang.uts\"],\"names\":[],\"mappings\":\"AAAA,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,MAAM,KAAK,CAAA;AACzD,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,MAAM,KAAK,CAAA;MAEhG,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcb,eAAe,aAAa,CAAA,gBAAgB,CAAC;IAC3C,MAAM,EAAE,WAAW;IACnB,KAAK,EAAE;QACL,QAAQ,EAAE,EAAE;KACb;IACD,KAAK,EAAE,CAAC,OAAO,CAAC;IAChB,KAAK,CAAC,OAAY,EAAE,EAAgB;YAAR,MAAM,UAAA;QAEnC,YAAY;QACZ,MAAM,KAAK,GAAG,OAAO,CAAA;QAErB,MAAM,IAAI,GAAG,MAAM,CAAA;QAEnB,MAAM,WAAW,GAAG;YACnB,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QACjC,CAAC,CAAA;QAEF,OAAO,CAAC,IAAI,EAAE,MAAM;YAAO,QAAQ,CAAA;YACjC,MAAM,YAAY,GAAG;gBACrB,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU;gBAC3B,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;gBACjC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAC1B,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAC7B,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAC7B,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAC1B,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAC1B,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAC1B,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC;gBAC/B,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC;aACnB,CAAA;YACC,OAAO,YAAY,CAAA;QACrB,CAAC,CAAA;IACD,CAAC;CAEA,CAAC,CAAA\"}"}

View File

@@ -0,0 +1 @@
{"code":"import '\u0000plugin-vue:export-helper';\nimport 'uni-mp-runtime';\nimport './pages-json-js';\nimport App from './App.uvue';\nimport { createSSRApp } from 'vue';\nexport function createApp() {\n const app = createSSRApp(App);\n return {\n app\n };\n}\n;\ncreateApp().app.mount(\"#app\");\n//# sourceMappingURL=D:/Project/Self/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/main.uts.js.map","references":["D:/Project/Self/优艺家沙发翻新/优艺家沙发翻新/App.uvue.ts","D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts"],"uniExtApis":[],"map":"{\"version\":3,\"file\":\"main.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"main.uts\"],\"names\":[],\"mappings\":\"AAAA,OAAO,2BAA2B,CAAC;AAAA,OAAO,gBAAgB,CAAC;AAAA,OAAO,iBAAiB,CAAC;AAAA,OAAO,GAAG,MAAM,YAAY,CAAA;AAEhH,OAAO,EAAE,YAAY,EAAE,MAAM,KAAK,CAAA;AAClC,MAAM,UAAU,SAAS;IACxB,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC7B,OAAO;QACN,GAAG;KACH,CAAA;AACF,CAAC;AAAA,CAAC;AACF,SAAS,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC\"}"}

View File

@@ -0,0 +1 @@
{"code":"import { defineComponent as _defineComponent } from 'vue';\nimport { toDisplayString as _toDisplayString, t as _t, o as _o, gei as _gei, sei as _sei, e as _e } from \"vue\";\nexport default /*#__PURE__*/ _defineComponent({\n __name: 'section-header',\n props: {\n title: {},\n showMore: { type: Boolean }\n },\n emits: [\"more\"],\n setup(__props, _a) {\n var __emit = _a.emit;\n const props = __props;\n const emit = __emit;\n const handleMore = () => {\n emit('more');\n };\n return (_ctx, _cache) => {\n \"raw js\";\n const __returned__ = _e({\n a: _t(_ctx.title),\n b: _ctx.showMore\n }, _ctx.showMore ? {\n c: _o(handleMore)\n } : {}, {\n d: _sei(_gei(_ctx, ''), 'view')\n });\n return __returned__;\n };\n }\n});\n//# sourceMappingURL=D:/Project/Self/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/%E4%BC%98%E8%89%BA%E5%AE%B6%E6%B2%99%E5%8F%91%E7%BF%BB%E6%96%B0/components/section-header/section-header.uvue?vue&type=script&setup=true&lang.uts.js.map","references":["D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts","D:/Soft/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts"],"uniExtApis":[],"map":"{\"version\":3,\"file\":\"section-header.uvue?vue&type=script&setup=true&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"section-header.uvue?vue&type=script&setup=true&lang.uts\"],\"names\":[],\"mappings\":\"AAAA,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,MAAM,KAAK,CAAA;AACzD,OAAO,EAAE,eAAe,IAAI,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,MAAM,KAAK,CAAA;AAG9G,eAAe,aAAa,CAAA,gBAAgB,CAAC;IAC3C,MAAM,EAAE,gBAAgB;IACxB,KAAK,EAAE;QACL,KAAK,EAAE,EAAE;QACT,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;KAC5B;IACD,KAAK,EAAE,CAAC,MAAM,CAAC;IACf,KAAK,CAAC,OAAY,EAAE,EAAgB;YAAR,MAAM,UAAA;QAEnC,MAAM,KAAK,GAAG,OAAO,CAAA;QAErB,MAAM,IAAI,GAAG,MAAM,CAAA;QAEnB,MAAM,UAAU,GAAG;YAClB,IAAI,CAAC,MAAM,CAAC,CAAA;QACb,CAAC,CAAA;QAEF,OAAO,CAAC,IAAI,EAAE,MAAM;YAAO,QAAQ,CAAA;YACjC,MAAM,YAAY,GAAG,EAAE,CAAC;gBACxB,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;gBACjB,CAAC,EAAE,IAAI,CAAC,QAAQ;aACjB,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACjB,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC;aAClB,CAAC,CAAC,CAAC,EAAE,EAAE;gBACN,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC;aAChC,CAAC,CAAA;YACA,OAAO,YAAY,CAAA;QACrB,CAAC,CAAA;IACD,CAAC;CAEA,CAAC,CAAA\"}"}

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sources":["api/index.uts"],"sourcesContent":["/**\r\n * API接口统一管理\r\n */\r\nimport { get, post } from '../utils/request.uts'\r\n\r\n/**\r\n * 获取轮播图 (临时从配置获取,后续可从后端获取)\r\n */\r\nexport const getBanners = () => {\r\n\t// 由于后端暂时没有轮播图API先返回固定数据\r\n\treturn Promise.resolve({\r\n\t\tcode: 0,\r\n\t\tmessage: 'success',\r\n\t\tdata: [\r\n\t\t\t{\r\n\t\t\t\tid: '1',\r\n\t\t\t\timage: '/static/mock/banner1.svg',\r\n\t\t\t\ttitle: '专业沙发翻新服务',\r\n\t\t\t\tlink: '/pages/service/index'\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tid: '2',\r\n\t\t\t\timage: '/static/mock/banner2.svg', \r\n\t\t\t\ttitle: '十年品质保证',\r\n\t\t\t\tlink: '/pages/about/index'\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tid: '3',\r\n\t\t\t\timage: '/static/mock/banner3.svg',\r\n\t\t\t\ttitle: '免费上门评估', \r\n\t\t\t\tlink: '/pages/booking/index'\r\n\t\t\t}\r\n\t\t]\r\n\t})\r\n}\r\n\r\n/**\r\n * 获取案例列表\r\n */\r\nexport const getCaseList = (params ?: UTSJSONObject) => {\r\n\t// 后端使用page和limit参数需要转换\r\n\tconst queryParams = params ? {\r\n\t\tpage: params['page'] || 1,\r\n\t\tlimit: params['pageSize'] || params['limit'] || 10,\r\n\t\tserviceType: params['category'], // 后端使用serviceType\r\n\t\tstatus: 'published' // 只获取已发布的案例\r\n\t} : {}\r\n\treturn get('/cases', queryParams as UTSJSONObject)\r\n}\r\n\r\n/**\r\n * 获取案例详情\r\n */\r\nexport const getCaseDetail = (id : string) => {\r\n\treturn get(`/cases/${id}`)\r\n}\r\n\r\n/**\r\n * 获取热门案例 (临时使用前4条案例)\r\n */\r\nexport const getHotCases = () => {\r\n\treturn get('/cases', { limit: 4, status: 'published' } as UTSJSONObject)\r\n}\r\n\r\n/**\r\n * 获取服务流程 (使用服务列表替代)\r\n */\r\nexport const getServiceProcess = () => {\r\n\treturn get('/services')\r\n}\r\n\r\n/**\r\n * 获取有效服务列表\r\n */\r\nexport const getActiveServices = () => {\r\n\treturn get('/services/active')\r\n}\r\n\r\n/**\r\n * 获取公司信息\r\n */\r\nexport const getCompanyInfo = () => {\r\n\treturn get('/company/info')\r\n}\r\n\r\n/**\r\n * 提交预约\r\n */\r\nexport const submitBooking = (data : UTSJSONObject) => {\r\n\treturn post('/booking', data)\r\n}\r\n\r\n/**\r\n * 获取用户信息\r\n */\r\nexport const getUserInfo = () => {\r\n\treturn get('/user/info')\r\n}\r\n\r\n/**\r\n * 获取用户收藏列表\r\n */\r\nexport const getFavorites = () => {\r\n\treturn get('/user/favorites')\r\n}\r\n\r\n/**\r\n * 添加收藏\r\n */\r\nexport const addFavorite = (caseId : string) => {\r\n\treturn post('/user/favorites', { caseId: caseId } as UTSJSONObject)\r\n}\r\n\r\n/**\r\n * 取消收藏\r\n */\r\nexport const removeFavorite = (caseId : string) => {\r\n\treturn post('/user/favorites/remove', { caseId: caseId } as UTSJSONObject)\r\n}\r\n\r\n/**\r\n * 获取预约记录\r\n */\r\nexport const getBookingList = () => {\r\n\treturn get('/user/bookings')\r\n}\r\n"],"names":["get","post"],"mappings":";;AAQa,MAAA,aAAa,MAAA;AAEzB,SAAO,QAAQ,QAAQ,IAAA,cAAA;AAAA,IACtB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,MAAM;AAAA,MACL,IAAA,cAAA;AAAA,QACC,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM;AAAA,MACN,CAAA;AAAA,MACD,IAAA,cAAA;AAAA,QACC,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM;AAAA,MACN,CAAA;AAAA,MACD,IAAA,cAAA;AAAA,QACC,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM;AAAA,MACN,CAAA;AAAA,IACD;AAAA,EACD,CAAA,CAAA;AACF;AAKO,MAAM,cAAc,CAAC,SAAuB,SAAA;AAElD,QAAM,cAAc,SAAS,IAAA,cAAA;AAAA,IAC5B,MAAM,OAAO,MAAM,KAAK;AAAA,IACxB,OAAO,OAAO,UAAU,KAAK,OAAO,OAAO,KAAK;AAAA,IAChD,aAAa,OAAO,UAAU;AAAA,IAC9B,QAAQ;AAAA;AAAA,EACR,CAAA,IAAE,IAAA,cAAC,CAAA,CAAE;AACN,SAAOA,cAAG,IAAC,UAAU,WAA4B;AAClD;AAKO,MAAM,gBAAgB,CAAC,OAAW;AACxC,SAAOA,cAAG,IAAC,UAAU,EAAE,EAAE;AAC1B;AAKa,MAAA,cAAc,MAAA;AAC1B,SAAOA,cAAG,IAAC,UAAQ,IAAA,cAAE,EAAE,OAAO,GAAG,QAAQ,YAA8B,CAAA;AACxE;AAKa,MAAA,oBAAoB,MAAA;AAChC,SAAOA,cAAAA,IAAI,WAAW;AACvB;AAYa,MAAA,iBAAiB,MAAA;AAC7B,SAAOA,cAAAA,IAAI,eAAe;AAC3B;AAKO,MAAM,gBAAgB,CAAC,SAAoB;AACjD,SAAOC,cAAI,KAAC,YAAY,IAAI;AAC7B;;;;;;;;"}

View File

@@ -0,0 +1 @@
{"version":3,"file":"app.js","sources":["App.uvue","main.uts"],"sourcesContent":["<script lang=\"uts\">\r\n\r\n\r\n\r\n\texport default {\r\n\t\tonLaunch() {\r\n\t\t\tuni.__f__('log','at App.uvue:7','App Launch')\r\n\t\t},\r\n\t\tonShow() {\r\n\t\t\tuni.__f__('log','at App.uvue:10','App Show')\r\n\t\t},\r\n\t\tonHide() {\r\n\t\t\tuni.__f__('log','at App.uvue:13','App Hide')\r\n\t\t},\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\tonExit() {\r\n\t\t\tuni.__f__('log','at App.uvue:34','App Exit')\r\n\t\t},\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t/*每个页面公共css */\r\n\t.uni-row {\r\n\t\tflex-direction: row;\r\n\t}\r\n\r\n\t.uni-column {\r\n\t\tflex-direction: column;\r\n\t}\r\n</style>","import App from './App.uvue'\r\n\r\nimport { createSSRApp } from 'vue'\r\nexport function createApp() {\r\n\tconst app = createSSRApp(App)\r\n\treturn {\r\n\t\tapp\r\n\t}\r\n}"],"names":["defineComponent","uni","createSSRApp","App"],"mappings":";;;;;;;;;;;;AAIC,MAAA,YAAeA,8BAAA;AAAA,EACd,WAAQ;AACPC,kBAAAA,MAAI,MAAM,OAAM,iBAAgB,YAAY;AAAA,EAC5C;AAAA,EACD,SAAM;AACLA,kBAAAA,MAAI,MAAM,OAAM,kBAAiB,UAAU;AAAA,EAC3C;AAAA,EACD,SAAM;AACLA,kBAAAA,MAAI,MAAM,OAAM,kBAAiB,UAAU;AAAA,EAC3C;AAAA,EAmBD,SAAM;AACLA,kBAAAA,MAAI,MAAM,OAAM,kBAAiB,UAAU;AAAA,EAC3C;CACF;SChCe,YAAS;AACxB,QAAM,MAAMC,2BAAaC,SAAG;AAC5B,SAAO;AAAA,IACN;AAAA;AAEF;AACA,YAAY,IAAI,MAAM,MAAM;;"}

View File

@@ -0,0 +1 @@
{"version":3,"file":"assets.js","sources":["static/logo.png"],"sourcesContent":["export default \"__VITE_ASSET__46719607__\""],"names":[],"mappings":";AAAA,MAAe,aAAA;;"}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"before-after.js","sources":["components/before-after/before-after.uvue","../../../../Soft/HBuilderX/plugins/uniapp-cli-vite/uniComponent:/RDovUHJvamVjdC9TZWxmL-S8mOiJuuWutuaymeWPkee_u-aWsC_kvJjoibrlrrbmspnlj5Hnv7vmlrAvY29tcG9uZW50cy9iZWZvcmUtYWZ0ZXIvYmVmb3JlLWFmdGVyLnV2dWU"],"sourcesContent":["<template>\r\n\t<view class=\"before-after\">\r\n\t\t<view class=\"ba-title\" v-if=\"showTitle\">\r\n\t\t\t<text class=\"ba-title-text\">翻新前后对比</text>\r\n\t\t</view>\r\n\t\t\r\n\t\t<view class=\"ba-container\">\r\n\t\t\t<!-- 翻新前 -->\r\n\t\t\t<view class=\"ba-item\" @click=\"previewImage(beforeImage, 'before')\">\r\n\t\t\t\t<view class=\"ba-label before-label\">\r\n\t\t\t\t\t<text class=\"ba-label-text\">翻新前</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<image \r\n\t\t\t\t\tclass=\"ba-image\" \r\n\t\t\t\t\t:src=\"beforeImage\" \r\n\t\t\t\t\tmode=\"aspectFill\"\r\n\t\t\t\t></image>\r\n\t\t\t</view>\r\n\t\t\t\r\n\t\t\t<!-- 箭头 -->\r\n\t\t\t<view class=\"ba-arrow\">\r\n\t\t\t\t<text class=\"ba-arrow-text\">→</text>\r\n\t\t\t</view>\r\n\t\t\t\r\n\t\t\t<!-- 翻新后 -->\r\n\t\t\t<view class=\"ba-item\" @click=\"previewImage(afterImage, 'after')\">\r\n\t\t\t\t<view class=\"ba-label after-label\">\r\n\t\t\t\t\t<text class=\"ba-label-text\">翻新后</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<image \r\n\t\t\t\t\tclass=\"ba-image\" \r\n\t\t\t\t\t:src=\"afterImage\" \r\n\t\t\t\t\tmode=\"aspectFill\"\r\n\t\t\t\t></image>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\n\tconst props = defineProps<{\r\n\t\tbeforeImage : string\r\n\t\tafterImage : string\r\n\t\tshowTitle ?: boolean\r\n\t}>()\r\n\t\r\n\t// 预览图片\r\n\tconst previewImage = (url : string, type : string) => {\r\n\t\tconst urls = type == 'before' ? [props.beforeImage] : [props.afterImage]\r\n\t\tuni.previewImage({\r\n\t\t\tcurrent: url,\r\n\t\t\turls: urls\r\n\t\t})\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\">\r\n\t.before-after {\r\n\t\tbackground-color: #ffffff;\r\n\t\tborder-radius: 16rpx;\r\n\t\tpadding: 24rpx;\r\n\t\tmargin-bottom: 24rpx;\r\n\t}\r\n\t\r\n\t.ba-title {\r\n\t\tmargin-bottom: 24rpx;\r\n\t}\r\n\t\r\n\t.ba-title-text {\r\n\t\tfont-size: 32rpx;\r\n\t\tfont-weight: 600;\r\n\t\tcolor: #333333;\r\n\t}\r\n\t\r\n\t.ba-container {\r\n\t\tflex-direction: row;\r\n\t\talign-items: center;\r\n\t\tjustify-content: space-between;\r\n\t}\r\n\t\r\n\t.ba-item {\r\n\t\tflex: 1;\r\n\t\tposition: relative;\r\n\t\tborder-radius: 12rpx;\r\n\t\toverflow: hidden;\r\n\t}\r\n\t\r\n\t.ba-image {\r\n\t\twidth: 100%;\r\n\t\theight: 280rpx;\r\n\t\tborder-radius: 12rpx;\r\n\t}\r\n\t\r\n\t.ba-label {\r\n\t\tposition: absolute;\r\n\t\tbottom: 16rpx;\r\n\t\tleft: 16rpx;\r\n\t\tpadding: 8rpx 16rpx;\r\n\t\tborder-radius: 8rpx;\r\n\t\tz-index: 1;\r\n\t}\r\n\t\r\n\t.before-label {\r\n\t\tbackground-color: rgba(144, 147, 153, 0.9);\r\n\t}\r\n\t\r\n\t.after-label {\r\n\t\tbackground-color: rgba(212, 165, 116, 0.9);\r\n\t}\r\n\t\r\n\t.ba-label-text {\r\n\t\tfont-size: 22rpx;\r\n\t\tcolor: #ffffff;\r\n\t}\r\n\t\r\n\t.ba-arrow {\r\n\t\tpadding: 0 16rpx;\r\n\t}\r\n\t\r\n\t.ba-arrow-text {\r\n\t\tfont-size: 40rpx;\r\n\t\tcolor: #D4A574;\r\n\t\tfont-weight: bold;\r\n\t}\r\n</style>\r\n","import Component from 'D:/Project/Self/优艺家沙发翻新/优艺家沙发翻新/components/before-after/before-after.uvue'\nwx.createComponent(Component)"],"names":["uni","Component"],"mappings":";;;;;;;;;;AAwCC,UAAM,QAAQ;AAOd,UAAM,eAAe,CAAC,KAAc,SAAa;AAChD,YAAM,OAAO,QAAQ,WAAW,CAAC,MAAM,WAAW,IAAI,CAAC,MAAM,UAAU;AACvEA,oBAAAA,MAAI,aAAa;AAAA,QAChB,SAAS;AAAA,QACT;AAAA,MACA,CAAA;AAAA,IACF;;;;;;;;;;;;;;;;;;;;ACpDD,GAAG,gBAAgBC,SAAS;"}

View File

@@ -0,0 +1 @@
{"version":3,"file":"case-card.js","sources":["components/case-card/case-card.uvue","../../../../Soft/HBuilderX/plugins/uniapp-cli-vite/uniComponent:/RDovUHJvamVjdC9TZWxmL-S8mOiJuuWutuaymeWPkee_u-aWsC_kvJjoibrlrrbmspnlj5Hnv7vmlrAvY29tcG9uZW50cy9jYXNlLWNhcmQvY2FzZS1jYXJkLnV2dWU"],"sourcesContent":["<template>\r\n\t<view class=\"case-card\" @click=\"handleClick\">\r\n\t\t<!-- 封面图 -->\r\n\t\t<view class=\"card-image-wrapper\">\r\n\t\t\t<image \r\n\t\t\t\tclass=\"card-image\" \r\n\t\t\t\t:src=\"caseData.coverImage\" \r\n\t\t\t\tmode=\"aspectFill\"\r\n\t\t\t></image>\r\n\t\t\t<view class=\"card-category\">{{ caseData.categoryName }}</view>\r\n\t\t</view>\r\n\t\t\r\n\t\t<!-- 内容区域 -->\r\n\t\t<view class=\"card-content\">\r\n\t\t\t<text class=\"card-title\">{{ caseData.title }}</text>\r\n\t\t\t<view class=\"card-info\">\r\n\t\t\t\t<view class=\"info-item\">\r\n\t\t\t\t\t<text class=\"info-label\">材质:</text>\r\n\t\t\t\t\t<text class=\"info-value\">{{ caseData.material }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"info-item\">\r\n\t\t\t\t\t<text class=\"info-label\">工期:</text>\r\n\t\t\t\t\t<text class=\"info-value\">{{ caseData.duration }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t\t<view class=\"card-footer\">\r\n\t\t\t\t<text class=\"card-price\">{{ caseData.price }}</text>\r\n\t\t\t\t<view class=\"card-stats\">\r\n\t\t\t\t\t<text class=\"stat-item\">👁 {{ caseData.views }}</text>\r\n\t\t\t\t\t<text class=\"stat-item\">❤ {{ caseData.likes }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script setup lang=\"uts\">\r\n\t// 定义Props类型\r\n\ttype CaseItem = {\r\n\t\tid : string\r\n\t\ttitle : string\r\n\t\tcategory : string\r\n\t\tcategoryName : string\r\n\t\tcoverImage : string\r\n\t\tmaterial : string\r\n\t\tduration : string\r\n\t\tprice : string\r\n\t\tviews : number\r\n\t\tlikes : number\r\n\t}\r\n\t\r\n\tconst props = defineProps<{\r\n\t\tcaseData : CaseItem\r\n\t}>()\r\n\t\r\n\tconst emit = defineEmits<{\r\n\t\t(e : 'click', id : string) : void\r\n\t}>()\r\n\t\r\n\tconst handleClick = () => {\r\n\t\temit('click', props.caseData.id)\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\">\r\n\t.case-card {\r\n\t\tbackground-color: #ffffff;\r\n\t\tborder-radius: 16rpx;\r\n\t\toverflow: hidden;\r\n\t\tbox-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);\r\n\t\tmargin-bottom: 24rpx;\r\n\t}\r\n\t\r\n\t.card-image-wrapper {\r\n\t\tposition: relative;\r\n\t\twidth: 100%;\r\n\t\theight: 360rpx;\r\n\t}\r\n\t\r\n\t.card-image {\r\n\t\twidth: 100%;\r\n\t\theight: 100%;\r\n\t}\r\n\t\r\n\t.card-category {\r\n\t\tposition: absolute;\r\n\t\ttop: 16rpx;\r\n\t\tleft: 16rpx;\r\n\t\tbackground-color: rgba(212, 165, 116, 0.9);\r\n\t\tcolor: #ffffff;\r\n\t\tfont-size: 22rpx;\r\n\t\tpadding: 8rpx 16rpx;\r\n\t\tborder-radius: 8rpx;\r\n\t}\r\n\t\r\n\t.card-content {\r\n\t\tpadding: 24rpx;\r\n\t}\r\n\t\r\n\t.card-title {\r\n\t\tfont-size: 32rpx;\r\n\t\tfont-weight: 600;\r\n\t\tcolor: #333333;\r\n\t\tmargin-bottom: 16rpx;\r\n\t\tlines: 1;\r\n\t\ttext-overflow: ellipsis;\r\n\t}\r\n\t\r\n\t.card-info {\r\n\t\tmargin-bottom: 16rpx;\r\n\t}\r\n\t\r\n\t.info-item {\r\n\t\tflex-direction: row;\r\n\t\tmargin-bottom: 8rpx;\r\n\t}\r\n\t\r\n\t.info-label {\r\n\t\tfont-size: 26rpx;\r\n\t\tcolor: #909399;\r\n\t}\r\n\t\r\n\t.info-value {\r\n\t\tfont-size: 26rpx;\r\n\t\tcolor: #606266;\r\n\t}\r\n\t\r\n\t.card-footer {\r\n\t\tflex-direction: row;\r\n\t\tjustify-content: space-between;\r\n\t\talign-items: center;\r\n\t\tmargin-top: 16rpx;\r\n\t\tpadding-top: 16rpx;\r\n\t\tborder-top-width: 1rpx;\r\n\t\tborder-top-style: solid;\r\n\t\tborder-top-color: #EBEEF5;\r\n\t}\r\n\t\r\n\t.card-price {\r\n\t\tfont-size: 36rpx;\r\n\t\tfont-weight: 600;\r\n\t\tcolor: #D4A574;\r\n\t}\r\n\t\r\n\t.card-stats {\r\n\t\tflex-direction: row;\r\n\t}\r\n\t\r\n\t.stat-item {\r\n\t\tfont-size: 24rpx;\r\n\t\tcolor: #909399;\r\n\t\tmargin-left: 24rpx;\r\n\t}\r\n</style>\r\n","import Component from 'D:/Project/Self/优艺家沙发翻新/优艺家沙发翻新/components/case-card/case-card.uvue'\nwx.createComponent(Component)"],"names":["Component"],"mappings":";;MAsCM,iBAAQ,IAAA,QAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAab,UAAM,QAAQ;AAId,UAAM,OAAO;AAIb,UAAM,cAAc,MAAA;AACnB,WAAK,SAAS,MAAM,SAAS,EAAE;AAAA,IAChC;;;;;;;;;;;;;;;;;;;AC5DD,GAAG,gBAAgBA,SAAS;"}

Some files were not shown because too many files have changed in this diff Show More