替换图片并删掉本地图片,代码分包

This commit is contained in:
zhouyanli
2026-04-21 15:48:52 +08:00
parent b0d90c27db
commit 65e6e5bf30
70 changed files with 316 additions and 212 deletions

View File

@@ -0,0 +1,408 @@
<template>
<view class="repair-page">
<view class="status_bar"></view>
<!-- 顶部导航栏 -->
<view class="nav-bar">
<view class="nav-left" @tap="goBack">
<image src="http://47.104.199.163:9090/images/home/arr-left.png" style="width: 48rpx; height: 48rpx;"></image>
</view>
<view class="nav-title">在线报修</view>
<view class="nav-right"></view>
</view>
<!-- 报修类型Tab -->
<view class="repair-type-tabs">
<view
class="type-tab"
:class="{ active: activeType === 'public' }"
@click="publicClick"
style="margin-right: 20rpx;"
>
<image src="http://47.104.199.163:9090/images/repair/publicRepair.png" style="width: 100%;height: 100%;"></image>
<!-- <uni-icons type="staff" size="24" color="#fff"></uni-icons> -->
<!-- <text class="type-text">公共报修</text> -->
</view>
<view
class="type-tab"
:class="{ active: activeType === 'personal' }"
@click="personalClick"
>
<image src="http://47.104.199.163:9090/images/repair/personalRepair.png" style="width: 100%;height: 100%;"></image>
<!-- <uni-icons type="person" size="24" color="#fff"></uni-icons> -->
<!-- <text class="type-text">个人报修</text> -->
</view>
</view>
<view style="margin: 14px 0px;">
<text class="repair-title">报修记录</text>
</view>
<!-- 报修记录状态Tab -->
<view class="record-status-tabs">
<text
class="status-tab"
:class="{ active: activeStatus === 'pending' }"
@click="activeStatus = 'pending'" >
待派单
</text>
<text
class="status-tab"
:class="{ active: activeStatus === 'assigned' }"
@click="activeStatus = 'assigned'"
>
已派单
</text>
<text
class="status-tab"
:class="{ active: activeStatus === 'completed' }"
@click="activeStatus = 'completed'"
>
已完成
</text>
</view>
<!-- 报修列表 -->
<view class="repair-list">
<view class="repair-item" v-for="(item, index) in repairList" :key="index">
<!-- 标题和状态 -->
<view class="item-header">
<text class="item-title">标题标题标题</text>
<text class="item-status" :class="item.statusClass">{{ item.status }}</text>
</view>
<!-- 标签 -->
<view class="item-tags">
<text class="tag">公共报修</text>
<text class="tag">路灯</text>
</view>
<!-- 内容 -->
<view class="item-content">
{{ item.content }}
</view>
<!-- 图片占位 -->
<view class="item-imgs">
<view class="img-placeholder" v-for="(i,index) in item.imageList" :key="index">
<!-- <uni-icons type="image" size="20" color="#ccc"></uni-icons> -->
<image :src="i.img" style="width: 100%;height: 100%;"></image>
</view>
</view>
<!-- 时间和详情 -->
<view class="item-footer">
<text class="item-time">{{ item.time }}</text>
<text class="detail-btn" @click="goDetail(item)">报修详情</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
// 报修类型public:公共报修personal:个人报修)
const activeType = ref('public');
// 报修状态pending:待派单assigned:已派单completed:已完成)
const activeStatus = ref('pending');
// 模拟报修列表数据根据activeStatus过滤
const repairList = computed(() => {
let statusText = ""
let statusClass = ""
if(activeStatus.value === 'pending'){
statusText = '待派单',
statusClass = 'pending'
} else if(activeStatus.value === 'assigned'){
statusText = '已派单',
statusClass = 'assigned'
}else{
statusText = '已完成',
statusClass = 'completed'
}
// 原始数据
const mockData = [
{
status: statusText,
statusClass: statusClass,
content: '1栋楼下道路路灯不亮并且有的亮度不够请物业公司及时更换防止发生意外事故。',
time: '2023-08-01 10:05:46',
imageList:[
{img:'http://47.104.199.163:9090/images/repair/house1.png'},
{img:'http://47.104.199.163:9090/images/repair/house2.png'},
{img:'http://47.104.199.163:9090/images/repair/house3.png'},
]
},
{
status: statusText,
statusClass: statusClass,
content: '西门的门禁不好用了,草坪有好多的草,话题也应高擦了',
time: '2025-08-01 10:05:46',
imageList:[
{img:'http://47.104.199.163:9090/images/repair/house1.png'},
{img:'http://47.104.199.163:9090/images/repair/house2.png'},
{img:'http://47.104.199.163:9090/images/repair/house3.png'},
]
}
];
return mockData;
});
function publicClick(){
activeType.value = 'public'
uni.navigateTo({
url:"/pageSubPack/online-repair/publicRepair"
})
};
function personalClick(){
activeType.value = 'personal'
uni.navigateTo({
url:"/pageSubPack/online-repair/personalRepair"
})
}
// 返回上一页
const goBack = () => {
if(uni.getStorageSync('sourcePage') === "index"){
uni.switchTab({
url:"/pages/index/index"
})
}else if(uni.getStorageSync('sourcePage') === "serviceIndex"){
uni.switchTab({
url:"/pages/serviceIndex/serviceIndex"
})
}
uni.removeStorageSync('sourcePage')
}
// 跳转报修详情
const goDetail = (item) => {
// const itemStr = encodeURIComponent(JSON.stringify(item))
uni.navigateTo({
url: `/pageSubPack/online-repair/repairDetail?item = ${encodeURIComponent(JSON.stringify(item))}`
});
};
</script>
<style scoped>
/* 页面整体样式 */
.repair-page {
background: linear-gradient(to bottom left, #E2F0FF 0%, #f9f9f9 50%);
min-height: 100vh;
padding: 0 40rpx;
}
.status_bar {
height: 32px;
width: 100%;
}
/* 顶部导航栏 */
.nav-bar {
height: 90rpx;
display: flex;
align-items: center;
/* padding: 0 30rpx; */
color: #fff;
position: sticky;
top: 44px;
z-index: 100;
margin-bottom: 20px;
/* box-shadow: 0 4rpx 12rpx rgba(0, 122, 255, 0.2); */
}
.nav-left {
width: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.nav-title {
flex: 1;
text-align: center;
font-size: 32rpx;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 20rpx;
color: #000;
}
.nav-right {
width: 60rpx;
}
/* 报修类型Tab */
.repair-type-tabs {
display: flex;
/* background-color: #fff; */
/* padding: 10px; */
}
.type-tab {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100px;
border-radius: 8px;
color: #fff;
font-size: 14px;
}
.type-tab.active {
background: linear-gradient(135deg, #1E88E5 0%, #64B5F6 100%);
}
.type-tab:not(.active) {
background: linear-gradient(135deg, #4DB6AC 0%, #80CBC4 100%);
}
.type-text {
margin-top: 5px;
}
.repair-title{
color: #222222;
font-size: 16px;
font-weight: 600;
}
/* 报修状态Tab */
.record-status-tabs {
display: flex;
/* background-color: #fff; */
/* margin-top: 10px; */
/* padding: 0 15px; */
}
.status-tab {
flex: 1;
text-align: left;
height: 40px;
line-height: 40px;
font-size: 15px;
color: #666;
position: relative;
}
.status-tab.active {
color: #2F77FD;
font-weight: bold;
}
.status-tab.active::after {
content: '';
position: absolute;
bottom: 0;
left: 20%;
transform: translateX(-50%);
width: 45px;
height: 4px;
border-radius: 4rpx;
background-color: #1E88E5;
}
/* 报修列表 */
.repair-list {
padding: 28rpx 0rpx;
}
.repair-item {
background-color: #fff;
border-radius: 8px;
padding: 15px;
margin-bottom: 10px;
}
/* 列表项头部 */
.item-header {
display: flex;
justify-content: flex-start;
align-items: center;
margin-bottom: 10px;
}
.item-title {
font-size: 16px;
font-weight: bold;
color: #333;
padding-right: 20rpx;
}
.item-status {
font-size: 14px;
}
.item-status.pending {
/* 待派单-红色 */
color: #ffffff;
background-color: #E34D59;
border-radius: 20rpx;
font-size: 12px;
line-height: 22px;
padding: 0px 10px;
}
.item-status.assigned {
/* 已派单-绿色 */
color: #ffffff;
background-color: #09C2BD;
border-radius: 20rpx;
font-size: 12px;
line-height: 22px;
padding: 0px 10px;
}
.item-status.completed{
color: #ffffff;
background-color: #2F77FD;
border-radius: 20rpx;
font-size: 12px;
line-height: 22px;
padding: 0px 10px;
}
/* 标签 */
.item-tags {
/* display: flex; */
/* gap: 8px; */
margin-bottom: 10px;
}
.tag {
/* background-color: #e3f2fd; */
color: #999999;
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
border: 1px solid #999999;
margin-right: 8px;
}
/* 内容 */
.item-content {
font-size: 14px;
color: #666;
line-height: 1.5;
margin-bottom: 10px;
/* background-color: #d7e4f5; */
}
/* 图片占位 */
.item-imgs {
display: flex;
/* gap: 12px; */
margin-bottom: 10px;
}
.img-placeholder {
width: 80px;
height: 80px;
background-color: #f5f5f5;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 8px;
}
/* 底部时间和详情 */
.item-footer {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 28rpx;
}
.item-time {
color: #999;
}
.detail-btn {
color: #1E88E5;
}
</style>

View File

@@ -0,0 +1,639 @@
<template>
<view class="form-container">
<!-- 报修房屋 -->
<view class="form-item">
<text class="label">报修房屋</text>
<view class="value" @click="goToSelectHouse">
{{ houseName || '请选择房屋' }}
<text class="switch" v-if="houseName">切换</text>
<uni-icons type="arrowright" size="16"></uni-icons>
</view>
</view>
<!-- 维修项目 -->
<view class="form-item">
<text class="label">维修项目</text>
<view class="value" @click="goToSelectProject">
{{ projectName || '请选择维修项目' }}
<uni-icons type="arrowright" size="16"></uni-icons>
</view>
</view>
<!-- 标题 -->
<view class="form-item">
<text class="label">标题</text>
<uni-easyinput v-model="form.title" placeholder="请输入内容" :inputBorder="false"></uni-easyinput>
</view>
<!-- 问题描述 -->
<view class="form-item-wenti">
<text class="label">问题描述</text>
<uni-easyinput v-model="form.desc" placeholder="请描述你的问题" type="textarea"></uni-easyinput>
</view>
<!-- 手机号 -->
<view class="form-item">
<text class="label">手机号</text>
<uni-easyinput type="number" v-model="form.phone" placeholder="请输入手机号" :inputBorder="false"></uni-easyinput>
</view>
<!-- 预约时间 -->
<view class="form-item time-item" @click="openTimePopup">
<text class="label">预约时间</text>
<text class="value time-value">{{ showTimeText }}</text>
</view>
<!-- <view class="form-item">
<text class="label">预约时间</text>
<view class="value" @click="">
<uni-datetime-picker type="datetime" v-model="form.datetime" @change="changeTime" />
</view>
</view> -->
<!-- 问题照片 -->
<view class="form-item-wenti">
<text class="label">问题照片</text>
<view class="upload-wrap">
<!-- 已上传图片 -->
<view class="upload-item" v-for="(img, index) in form.images" :key="index">
<image :src="img" mode="aspectFill" @click="previewImage(img)"></image>
<uni-icons type="close" size="16" @click="deleteImage(index)"></uni-icons>
</view>
<!-- 上传按钮 -->
<view class="upload-btn" @click="chooseImage">
<uni-icons type="plus" size="24"></uni-icons>
<text>上传照片</text>
</view>
</view>
</view>
<!-- 提交按钮 -->
<button class="submit-btn" @click="submitForm">提交</button>
<!-- 时间选择弹窗 -->
<uni-popup ref="timePopupRef" type="bottom" :mask-click="false" border-radius="16rpx">
<view class="time-popup-container">
<!-- 弹窗头部取消 + 标题 + 确认 -->
<view class="time-popup-header">
<text class="cancel-btn" @click="closeTimePopup">取消</text>
<text class="popup-title">预约时间({{ tempTime.format('YYYY-MM-DD HH:mm') }})</text>
<text class="confirm-btn" @click="confirmTime">确认</text>
</view>
<!-- 时间选择列日期 | 小时 | 分钟 -->
<view class="time-selector">
<!-- 日期列 -->
<scroll-view
class="time-column"
scroll-y
:scroll-top="dateScrollTop"
@scroll="onDateScroll"
scroll-with-animation
>
<view
class="time-item"
:class="{ active: dateItem.value === selectedDate }"
v-for="(dateItem, index) in dateList"
:key="index"
@click="selectDate(index)"
>
{{ dateItem.value }}{{dateItem.label}}
</view>
</scroll-view>
<!-- 小时列 -->
<scroll-view
class="time-column"
scroll-y
:scroll-top="hourScrollTop"
@scroll="onHourScroll"
scroll-with-animation
>
<view
class="time-item"
:class="{ active: hourItem.time === selectedHour }"
v-for="(hourItem, index) in hourList"
:key="index"
@click="selectHour(index)"
>
{{ hourItem.time }}
</view>
</scroll-view>
<!-- 分钟列 -->
<scroll-view
class="time-column"
scroll-y
:scroll-top="minuteScrollTop"
@scroll="onMinuteScroll"
scroll-with-animation
>
<view
class="time-item"
:class="{ active: minuteItem.minute === selectedMinute }"
v-for="(minuteItem, index) in minuteList"
:key="index"
@click="selectMinute(index)"
>
{{ minuteItem.minute }}
</view>
</scroll-view>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref,computed,onMounted} from 'vue'
import {
onLoad, onUnload
} from '@dcloudio/uni-app'
import moment from 'moment/moment'
// 选中的房屋/项目名称
const houseName = ref('')
const projectName = ref('')
// 表单数据
const form = ref({
title: '',
desc: '',
phone: '',
datetime: '2023-08-01 15:30', // 默认时间(匹配截图)
images: []
})
// ========== 基础数据 ==========
const timePopupRef = ref(null)
// 初始时间()
const selectedTime = ref(moment().hour(9).minute(30).second(0));
// 选择的时间(弹窗中预览)
const tempTime = ref(moment().hour(9).minute(30).second(0));
// ========== 时间选择列数据 ==========
// 日期列表(今天、明天、后天)
const dateList = ref([
{ label: '(今天)', value: moment().format('YYYY-MM-DD') },
{ label: '(明天)', value: moment().add(1, 'day').format('YYYY-MM-DD') },
{ label: '(后天)', value: moment().add(2, 'day').format('YYYY-MM-DD') }
]);
// 小时列表()
const hourList = ref([
{time: moment().hours(8).format('HH')},
{time:moment().hours(9).format('HH')},
{time:moment().hours(10).format('HH')},
{time:moment().hours(11).format('HH')},
{time:moment().hours(13).format('HH')},
{time:moment().hours(14).format('HH')},
{time:moment().hours(15).format('HH')},
{time:moment().hours(16).format('HH')},
{time:moment().hours(17).format('HH')},
{time:moment().hours(18).format('HH')},
]);
// 分钟列表()
const minuteList = ref([
{minute:moment().minute(0).format('mm')},
{minute:moment().minute(30).format('mm')}
]);
// ========== 选中状态 ==========
const selectedDate = ref(dateList.value[0].value);
const selectedHour = ref(hourList.value[1].time); // 默认选中8点
const selectedMinute = ref(minuteList.value[1].minute); // 默认选中00分
// ========== 滚动配置 ==========
const itemHeight = 80; // 每个时间项的高度rpx
const dateScrollTop = ref(0 * itemHeight); // 初始选中明天
const hourScrollTop = ref(1 * itemHeight); // 初始选中9点
const minuteScrollTop = ref(1 * itemHeight); // 初始选中00分
// ========== 显示文本 ==========
const showTimeText = computed(() => {
return selectedTime.value.format('YYYY-MM-DD HH:mm');
});
// ========== Popup 操作 ==========
// 打开时间弹窗
const openTimePopup = () => {
// 打开前同步临时时间为当前选中时间
tempTime.value = moment(selectedTime.value);
selectedDate.value = tempTime.value.format('YYYY-MM-DD');
selectedHour.value = tempTime.value.format('HH');
selectedMinute.value = tempTime.value.format('mm');
// 同步滚动位置
syncScrollPosition();
// 打开uni-popup
if(timePopupRef.value){
timePopupRef.value.open();
}
};
// 关闭时间弹窗
const closeTimePopup = () => {
timePopupRef.value.close();
};
// 确认选择时间
const confirmTime = () => {
// 拼接选中的时间
const selectedDateTime = `${selectedDate.value} ${selectedHour.value}:${selectedMinute.value}`;
selectedTime.value = moment(selectedDateTime, 'YYYY-MM-DD HH:mm');
// 关闭弹窗
closeTimePopup();
// 提示
uni.showToast({
title: `已选择 ${selectedTime.value.format('MM月DD日 HH:mm')}`,
icon: 'none'
});
};
// ========== 滚动/点击选择逻辑 ==========
// 同步滚动位置
const syncScrollPosition = () => {
// 日期滚动位置
const dateIndex = dateList.value.findIndex(item => item.value === selectedDate.value);
dateScrollTop.value = dateIndex * itemHeight;
// 小时滚动位置
const hourIndex = hourList.value.findIndex(item => item.time === selectedHour.value);
hourScrollTop.value = hourIndex * itemHeight;
// 分钟滚动位置
const minuteIndex = minuteList.value.findIndex(item => item.minute === selectedMinute.value);
minuteScrollTop.value = minuteIndex * itemHeight;
};
// 选择日期(点击)
const selectDate = (index) => {
selectedDate.value = dateList.value[index].value;
dateScrollTop.value = index * itemHeight;
updateTempTime();
};
// 选择小时(点击)
const selectHour = (index) => {
selectedHour.value = hourList.value[index].time;
hourScrollTop.value = index * itemHeight;
updateTempTime();
};
// 选择分钟(点击)
const selectMinute = (index) => {
selectedMinute.value = minuteList.value[index].minute;
minuteScrollTop.value = index * itemHeight;
updateTempTime();
};
// 滚动日期列(滑动)
const onDateScroll = (e) => {
const index = Math.round(e.detail.scrollTop / itemHeight);
if (index >= 0 && index < dateList.value.length) {
selectedDate.value = dateList.value[index].value;
updateTempTime();
}
};
// 滚动小时列(滑动)
const onHourScroll = (e) => {
const index = Math.round(e.detail.scrollTop / itemHeight);
if (index >= 0 && index < hourList.value.length) {
selectedHour.value = hourList.value[index].time;
updateTempTime();
}
};
// 滚动分钟列(滑动)
const onMinuteScroll = (e) => {
const index = Math.round(e.detail.scrollTop / itemHeight);
minuteScrollTop.value = index * itemHeight;
if (index >= 0 && index < minuteList.value.length) {
selectedMinute.value = minuteList.value[index].minute;
updateTempTime();
}
};
// 更新临时时间(弹窗预览)
const updateTempTime = () => {
tempTime.value = moment(`${selectedDate.value} ${selectedHour.value}:${selectedMinute.value}`, 'YYYY-MM-DD HH:mm');
};
// ========== 初始化 ==========
onMounted(() => {
syncScrollPosition();
});
// 页面加载时监听事件
onLoad(() => {
// 监听选择房屋事件
uni.$on('selectHouse', (data) => {
houseName.value = data.name
})
// 监听选择维修项目事件
uni.$on('selectProject', (data) => {
projectName.value = data.name
})
})
// 页面销毁移除监听
onUnload(() => {
uni.$off('selectHouse')
uni.$off('selectProject')
})
// 跳转到选择房屋页
const goToSelectHouse = () => {
uni.navigateTo({
url: '/pageSubPack/online-repair/selectHouse'
})
}
// 跳转到选择维修项目页
const goToSelectProject = () => {
uni.navigateTo({
url: '/pageSubPack/online-repair/personalSelectPoject'
})
}
// 选择预约时间
const changeTime = () =>{
}
// const chooseDateTime = () => {
// uni.datePicker({
// type: 'datetime',
// value: form.value.datetime,
// success: (res) => {
// form.value.datetime = res.value
// }
// })
// }
// 选择图片
const chooseImage = () => {
uni.chooseImage({
count: 3,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
form.value.images = [...form.value.images, ...res.tempFilePaths]
}
})
}
// 预览图片
const previewImage = (url) => {
uni.previewImage({
urls: form.value.images,
current: url
})
}
// 删除图片
const deleteImage = (index) => {
form.value.images.splice(index, 1)
}
// 表单提交
const submitForm = () => {
// 校验
if (!houseName.value) {
uni.showToast({ title: '请选择报修房屋', icon: 'none' })
return
}
if (!projectName.value) {
uni.showToast({ title: '请选择维修项目', icon: 'none' })
return
}
if (!form.value.title) {
uni.showToast({ title: '请输入问题标题', icon: 'none' })
return
}
if (!form.value.desc) {
uni.showToast({ title: '请输入问题描述', icon: 'none' })
return
}
if (!/^1[3-9]\d{9}$/.test(form.value.phone)) {
uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
return
}
if (!form.value.datetime) {
uni.showToast({ title: '请选择预约时间', icon: 'none' })
return
}
// 提交逻辑(替换为你的接口)
uni.showLoading({ title: '提交中...' })
setTimeout(() => {
uni.hideLoading()
uni.showToast({ title: '提交成功', icon: 'success' })
// 重置表单
// form.value = { title: '', desc: '', phone: '', datetime: '', images: [] }
// houseName.value = ''
// projectName.value = ''
}, 1000)
}
</script>
<style scoped>
.form-container {
padding: 15px;
background: #fff;
min-height: 100vh;
}
.form-item {
display: flex;
margin-bottom: 30rpx;
align-items: flex-start;
border-bottom: 1px solid #eee;
}
.label {
width: 140rpx;
font-size: 28rpx;
color: #333;
line-height: 80rpx;
}
.value {
flex: 1;
font-size: 28rpx;
color: #666;
line-height: 80rpx;
text-align: right;
display: flex;
justify-content: space-between;
}
.time-value {
color: #000;
}
.input, .textarea {
flex: 1;
padding: 20rpx;
font-size: 28rpx;
border: 1px solid #eee;
border-radius: 8rpx;
}
.textarea {
min-height: 160rpx;
line-height: 40rpx;
}
.form-item-wenti {
margin-bottom: 20px;
/* border-bottom: 1px solid #eee; */
padding-bottom: 10px;
}
/* .label {
font-size: 14px;
color: #333;
margin-bottom: 8px;
display: block;
}
.value {
font-size: 14px;
color: #666;
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
} */
.switch {
font-size: 12px;
color: #007aff;
margin-right: 5px;
}
input, textarea {
font-size: 14px;
width: 100%;
padding: 8px 0;
box-sizing: border-box;
}
textarea {
resize: none;
}
.upload-wrap {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 8px;
}
.upload-item {
position: relative;
width: 80px;
height: 80px;
}
.upload-item image {
width: 100%;
height: 100%;
border-radius: 4px;
}
.upload-item uni-icons {
position: absolute;
top: -5px;
right: -5px;
background: #ff3b30;
color: #fff;
border-radius: 50%;
padding: 2px;
}
.upload-btn {
width: 80px;
height: 80px;
border: 1px dashed #ccc;
border-radius: 4px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #999;
font-size: 12px;
}
.submit-btn {
width: 100%;
background: #007aff;
color: #fff;
border-radius: 8px;
height: 44px;
line-height: 44px;
font-size: 16px;
margin-top: 20px;
}
/* ========== uni-popup 时间选择器样式 ========== */
.time-popup-container {
background-color: #fff;
border-top-left-radius: 16rpx;
border-top-right-radius: 16rpx;
}
/* 弹窗头部 */
.time-popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
border-bottom: 1px solid #eee;
}
.cancel-btn {
font-size: 28rpx;
color: #666;
}
.popup-title {
font-size: 28rpx;
color: #333;
}
.confirm-btn {
font-size: 28rpx;
color: #007aff;
font-weight: 600;
}
/* 时间选择器容器 */
.time-selector {
display: flex;
height: 400rpx;
padding: 20rpx 0;
}
/* 单个时间列 */
.time-column {
flex: 1;
height: 100%;
text-align: center;
}
/* 时间项 */
.time-item {
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
color: #666;
position: relative;
}
/* 选中项样式(高亮) */
.time-item.active {
color: #007aff;
font-weight: 600;
background-color: #d0d0d0;
}
.time-item.active::after {
content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
/* width: 80%; */
/* height: 1px; */
background-color: #d0d0d0;
}
</style>

View File

@@ -0,0 +1,179 @@
<template>
<view class="project-container">
<!-- 搜索框 -->
<view class="search-bar">
<input type="text" placeholder="请输入关键字搜索" v-model="searchKey" />
<uni-icons type="search" size="18"></uni-icons>
</view>
<!-- 分类+项目 -->
<view class="category-wrap">
<!-- 左侧分类 -->
<scroll-view scroll-y class="category-left">
<view
class="category-item"
:class="{ active: activeCategory === item.id }"
@click="selectCategory(item)"
v-for="item in categoryList"
:key="item.id"
>
{{ item.name }}
</view>
</scroll-view>
<!-- 右侧项目 -->
<scroll-view scroll-y class="category-right">
<view
class="project-item"
:class="{ active: selectedProject.id === item.id }"
@click="selectProject(item)"
v-for="item in currentProjectList"
:key="item.id"
>
{{ item.name }}
</view>
</scroll-view>
</view>
<!-- 确认按钮 -->
<button class="confirm-btn" @click="confirmSelect">确认</button>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
// import { uniShowToast, uniNavigateBack } from '@dcloudio/uni-app'
// 分类列表(匹配截图:家具/电路/水暖/门频/电器)
const categoryList = ref([
{ id: 1, name: '家具' },
{ id: 2, name: '电路' },
{ id: 3, name: '水暖' },
{ id: 4, name: '门频' },
{ id: 5, name: '电器' }
])
// 项目列表(匹配截图:床/沙发/柜子/椅子等)
const projectList = ref([
{ id: 101, categoryId: 1, name: '床' },
{ id: 102, categoryId: 1, name: '沙发' },
{ id: 103, categoryId: 1, name: '柜子' },
{ id: 104, categoryId: 1, name: '椅子' },
{ id: 201, categoryId: 2, name: '插座' },
{ id: 202, categoryId: 2, name: '开关' },
{ id: 301, categoryId: 3, name: '水龙头' },
{ id: 302, categoryId: 3, name: '水管' }
])
// 选中状态
const activeCategory = ref(1) // 默认选中家具
const selectedProject = ref({}) // 选中的项目
const searchKey = ref('')
// 筛选当前分类的项目
const currentProjectList = computed(() => {
let list = projectList.value.filter(item => item.categoryId === activeCategory.value)
if (searchKey.value) {
list = list.filter(item => item.name.includes(searchKey.value))
}
return list
})
// 选择分类
const selectCategory = (item) => {
activeCategory.value = item.id
selectedProject.value = {} // 切换分类清空选中项目
}
// 选择项目
const selectProject = (item) => {
selectedProject.value = item
}
// 确认选择
const confirmSelect = () => {
if (!selectedProject.value.id) {
uni.showToast({ title: '请选择维修项目', icon: 'none' })
return
}
// 获取分类名称
const categoryName = categoryList.value.find(c => c.id === activeCategory.value).name
// 传递给表单页
uni.$emit('selectProject', {
name: `${categoryName}-${selectedProject.value.name}`
})
uni.navigateBack({ delta: 1 })
}
</script>
<style scoped>
.project-container {
padding: 15px;
height: 100vh;
box-sizing: border-box;
display: flex;
flex-direction: column;
background: #fff;
}
.search-bar {
display: flex;
align-items: center;
padding: 8px 10px;
background: #f5f5f5;
border-radius: 6px;
margin-bottom: 10px;
}
.search-bar input {
flex: 1;
font-size: 14px;
}
.category-wrap {
display: flex;
flex: 1;
margin-bottom: 20px;
}
.category-left {
width: 80px;
height: 100%;
background: #f8f8f8;
border-radius: 6px 0 0 6px;
}
.category-item {
padding: 12px 10px;
text-align: center;
font-size: 14px;
border-bottom: 1px solid #eee;
}
.category-item.active {
background: #fff;
color: #007aff;
font-weight: bold;
}
.category-right {
flex: 1;
height: 100%;
background: #fff;
border-radius: 0 6px 6px 0;
padding: 10px;
}
.project-item {
padding: 12px 10px;
font-size: 14px;
border-bottom: 1px solid #eee;
}
.project-item.active {
color: #007aff;
font-weight: bold;
}
.confirm-btn {
width: 100%;
height: 44px;
line-height: 44px;
background: #007aff;
color: #fff;
border-radius: 8px;
font-size: 16px;
}
</style>

View File

@@ -0,0 +1,245 @@
<template>
<view class="form-container">
<!-- 维修项目展示 -->
<view class="form-item">
<text class="label">维修项目</text>
<view class="value" @click="goBackSelect">
<view>
<text v-if="!repairProject">请选择项目</text>
{{ repairProject }}
</view>
<uni-icons type="arrowright" size="16"></uni-icons>
</view>
</view>
<!-- 标题输入 -->
<view class="form-item">
<text class="label">标题</text>
<!-- <input type="text" placeholder="请输入问题标题" v-model="form.title" /> -->
<uni-easyinput v-model="form.title" placeholder="请输入内容" :inputBorder="false"></uni-easyinput>
</view>
<!-- 问题描述 -->
<view class="form-item-wenti">
<text class="label">问题描述</text>
<!-- <textarea placeholder="请输入问题内容" v-model="form.desc" rows="5" ></textarea> -->
<uni-easyinput v-model="form.desc" placeholder="请描述你的问题" type="textarea"></uni-easyinput>
</view>
<!-- 手机号 -->
<view class="form-item">
<text class="label">手机号</text>
<uni-easyinput type="number" v-model="form.phone" placeholder="请输入手机号" :inputBorder="false"></uni-easyinput>
</view>
<!-- 图片上传 -->
<view class="form-item-wenti">
<text class="label">问题照片</text>
<view class="upload-wrap">
<!-- 已上传图片 -->
<view class="upload-item" v-for="(img, index) in form.images" :key="index">
<image :src="img" mode="aspectFill"></image>
<uni-icons type="close" size="16" @click="deleteImage(index)"></uni-icons>
</view>
<!-- 上传按钮 -->
<view class="upload-btn" @click="chooseImage">
<uni-icons type="plus" size="24"></uni-icons>
<text>上传照片</text>
</view>
</view>
</view>
<!-- 提交按钮 -->
<button class="submit-btn" @click="submitForm">提交</button>
</view>
</template>
<script setup>
import { ref } from 'vue'
import{onLoad} from "@dcloudio/uni-app"
// import { onLoad, uniShowToast, uniNavigateBack, uniChooseImage, uniUploadFile } from '@dcloudio/uni-app'
// 接收上个页面传的维修项目
const repairProject = ref('')
// 表单数据
const form = ref({
title: '',
desc: '',
phone: '',
images: [] // 图片列表
})
// 页面加载时获取传参
onLoad((options) => {
if (options.category && options.project) {
repairProject.value = `${options.category}-${options.project}`
}
})
// 返回选择项目页
const goBackSelect = () => {
uni.navigateTo({
url:"/pageSubPack/online-repair/repairSelect"
})
// uni.navigateBack({
// delta: 1
// })
}
// 选择图片
const chooseImage = () => {
uni.chooseImage({
count: 3, // 最多选3张
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
// 临时文件路径
const tempFilePaths = res.tempFilePaths
form.value.images = [...form.value.images, ...tempFilePaths]
// 【可选】如果需要上传到服务器,取消下面注释
// tempFilePaths.forEach(path => {
// uni.uploadFile({
// url: '你的上传接口地址',
// filePath: path,
// name: 'file',
// success: (uploadRes) => {
// console.log('上传成功', uploadRes)
// }
// })
// })
}
})
}
// 删除图片
const deleteImage = (index) => {
form.value.images.splice(index, 1)
}
// 表单提交
const submitForm = () => {
// 表单校验
if (!repairProject.value) {
uni.showToast({ title: '请选择维修项目', icon: 'none' })
return
}
if (!form.value.title) {
uni.showToast({ title: '请输入问题标题', icon: 'none' })
return
}
if (!form.value.desc) {
uni.showToast({ title: '请输入问题描述', icon: 'none' })
return
}
if (!/^1[3-9]\d{9}$/.test(form.value.phone)) {
uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
return
}
// 提交数据(替换成你的接口)
uni.showLoading({ title: '提交中...' })
// 模拟接口请求
setTimeout(() => {
uni.hideLoading()
uni.showToast({ title: '提交成功', icon: 'success' })
// 提交成功后重置表单/返回上一页
// form.value = { title: '', desc: '', phone: '', images: [] }
// 或返回上一页
uni.navigateBack({ delta: 3 })
}, 1000)
}
</script>
<style scoped>
.form-container {
padding: 15px;
background: #fff;
min-height: 100vh;
}
.form-item-wenti {
margin-bottom: 20px;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.form-item {
display: flex;
margin-bottom: 30rpx;
align-items: flex-start;
border-bottom: 1px solid #eee;
}
.label {
width: 140rpx;
font-size: 28rpx;
color: #333;
line-height: 80rpx;
}
.value {
flex: 1;
font-size: 28rpx;
color: #666;
line-height: 80rpx;
text-align: right;
display: flex;
justify-content: space-between;
}
.input, .textarea {
font-size: 14px;
width: 100%;
padding: 8px 0;
box-sizing: border-box;
}
.textarea {
resize: none;
}
.upload-wrap {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 8px;
}
.upload-item {
position: relative;
width: 80px;
height: 80px;
}
.upload-item image {
width: 100%;
height: 100%;
border-radius: 4px;
}
.upload-item uni-icons {
position: absolute;
top: -5px;
right: -5px;
background: #ff3b30;
color: #fff;
border-radius: 50%;
padding: 2px;
}
.upload-btn {
width: 80px;
height: 80px;
border: 1px dashed #ccc;
border-radius: 4px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #999;
font-size: 12px;
}
.submit-btn {
width: 100%;
background: #007aff;
color: #fff;
border-radius: 6px;
height: 44px;
line-height: 44px;
font-size: 16px;
}
</style>

View File

@@ -0,0 +1,273 @@
<template>
<view class="repair-detail-container">
<!-- 维修流程模块 -->
<view class="info-section">
<!-- <uni-section title="维修流程" type="line" style="margin-bottom: 20rpx;"> -->
<view class="section-title">维修流程</view>
<uni-row class="demo-uni-row" :width="nvueWidth">
<uni-col :span="12">
<up-steps :current="currentStep" direction="column" space="30" active-color="#007aff">
<up-steps-item title="待处理" desc="提交报修成功"></up-steps-item>
<up-steps-item title="已派单" desc="维修师傅已接单"></up-steps-item>
<up-steps-item title="已完成" desc="维修完成"></up-steps-item>
</up-steps>
</uni-col>
<uni-col :span="12">
<view class="step-time">2026-03-24 09:30:38</view>
<view class="step-time">2026-03-26 09:30:38</view>
<view class="step-time-3">2026-03-28 09:30:38</view>
</uni-col>
</uni-row>
<!-- </uni-section> -->
</view>
<!-- 报修信息模块 -->
<view class="info-section">
<!-- <uni-section title="报修信息" type="line" style="margin-bottom: 20rpx;"> -->
<view class="section-title">报修信息</view>
<view class="info-item">
<view class="info-label">报修房屋</view>
<view class="info-value">{{repairInfo.house}}</view>
</view>
<view class="info-item">
<view class="info-label">维修项目</view>
<view class="info-value">{{repairInfo.project}}</view>
</view>
<view class="info-item">
<view class="info-label">标题</view>
<view class="info-value">{{repairInfo.title}}</view>
</view>
<view class="info-item-desc">
<view class="info-label">问题描述</view>
<view class="info-value multi-line">{{repairInfo.description}}</view>
</view>
<view class="info-item">
<view class="info-label">手机号</view>
<view class="info-value">{{repairInfo.phone}}</view>
</view>
<view class="info-item">
<view class="info-label">报修时间</view>
<view class="info-value">{{repairInfo.time}}</view>
</view>
<view class="info-item-desc">
<view class="info-label">问题照片</view>
<view class="photo-container">
<view class="photo-item" v-for="(item, index) in 2" :key="index">
<uni-icons type="plusempty" size="24" color="#ccc"></uni-icons>
</view>
</view>
</view>
<!-- </uni-section> -->
</view>
<!-- 联系物业按钮 -->
<view class="btn-container">
<button class="contact-btn" @click="contactProperty">联系物业</button>
</view>
</view>
</template>
<script setup>
import {
ref
} from 'vue'
import {
onLoad
} from '@dcloudio/uni-app'
// 步骤条当前激活的步骤0=待处理1=已派单2=已完成)
const currentStep = ref(2)
const nvueWidth = ref('300')
// 报修信息数据
const repairInfo = ref({
house: '桂花园(别墅) 2号楼2层',
project: '照明-路灯',
title: '1栋楼下道路灯不亮了',
description: '1栋楼下道路路灯不亮并且有的亮度不够请物业公司及时更换防止发生意外事故。',
phone: '15688888888',
time: '2023-08-01 15:30'
})
onLoad((option) => {
const decodeStr = decodeURIComponent(JSON.stringify(option))
// let item = JSON.parse(decodeStr)
// repairInfo.value = optionStr
console.log('item', decodeStr)
const status = decodeStr.statusClass
// console.log('status',status)
switch (status) {
case 'pending': //待处理
currentStep.value = 0
break
case 'assignde':
currentStep.value = 1
break
case 'completed':
currentStep.value = 2
break
default:
currentStep.value = 0
}
})
// 联系物业按钮点击事件
const contactProperty = () => {
uni.showToast({
title: '正在联系物业...',
icon: 'none'
})
}
</script>
<style scoped>
.repair-detail-container {
padding: 16px;
background-color: #f5f5f5;
min-height: 100vh;
}
.process-section,
.info-section {
background-color: #fff;
border-radius: 8px;
padding: 13px;
margin-bottom: 16px;
/* margin-top: 20rpx; */
}
.repair-steps-container {
padding: 20rpx 30rpx;
background-color: #fff;
}
.title {
font-size: 32rpx;
font-weight: 600;
margin-bottom: 30rpx;
color: #333;
}
.step-time {
height: 54px;
line-height: 54px;
font-size: 12px;
color: #999;
/* font-size: 22rpx;
color: #999;
text-align: right;
margin-top: 5rpx; */
}
.step-time-3{
height: 38px;
line-height: 38px;
font-size: 12px;
color: #999;
}
/* -------- */
.section-title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 12px;
color: #333;
}
.slot-title {
position: absolute;
top: -10px;
font-size: 12px;
font-weight: 400;
}
.slot-desc {
font-size: 10px;
}
/* .step-time {
position: absolute;
right: 0;
top: 0;
font-size: 14px;
color: #999;
white-space: nowrap;
} */
.info-item {
display: flex;
padding: 12px 0;
/* border-bottom: 1px solid #f0f0f0; */
}
.info-item-desc {
/* display: flex; */
padding: 12px 0;
/* border-bottom: 1px solid #f0f0f0; */
}
.info-section.active {
color: #007AFF;
/* font-weight: 500; */
}
.info-item:last-child {
border-bottom: none;
}
.info-label {
width: 80px;
color: #222;
font-size: 14px;
}
.info-value {
flex: 1;
color: #666;
font-size: 14px;
}
.multi-line {
white-space: pre-wrap;
line-height: 1.5;
border: 1px solid #f0f0f0;
border-radius: 8rpx;
padding: 30rpx;
margin: 30rpx 0;
}
.photo-container {
display: flex;
gap: 12px;
margin-top: 4px;
}
.photo-item {
width: 80px;
height: 80px;
border: 1px dashed #ccc;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.btn-container {
padding: 16px;
}
.contact-btn {
background-color: #007AFF;
color: #fff;
border: none;
border-radius: 8px;
height: 48px;
font-size: 16px;
width: 100%;
}
.contact-btn::after {
border: none;
}
</style>

View File

@@ -0,0 +1,141 @@
<template>
<view class="select-container">
<!-- 搜索框 -->
<view class="search-bar">
<input type="text" placeholder="请输入关键字搜索" v-model="searchKey" />
<uni-icons type="search" size="18"></uni-icons>
</view>
<!-- 分类选择 -->
<view class="category-wrap">
<!-- 左侧分类 -->
<scroll-view scroll-y class="category-left">
<view
class="category-item"
:class="{ active: activeCategory === item.id }"
@click="selectCategory(item)"
v-for="item in categoryList"
:key="item.id"
>
{{ item.name }}
</view>
</scroll-view>
<!-- 右侧项目 -->
<scroll-view scroll-y class="category-right">
<view
class="project-item"
@click="selectProject(item)"
v-for="item in currentProjectList"
:key="item.id"
>
{{ item.name }}
</view>
</scroll-view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
// import { uniNavigateTo } from '@dcloudio/uni-app'
// 分类列表
const categoryList = ref([
{ id: 1, name: '基础设施' },
{ id: 2, name: '电子设施' }
])
// 所有项目数据
const projectList = ref([
{ id: 101, categoryId: 1, name: '路灯' },
{ id: 102, categoryId: 1, name: '楼层指示灯' },
{ id: 103, categoryId: 2, name: '应急出口灯' },
{ id: 104, categoryId: 1, name: '草坪灯' }
])
// 选中的分类
const activeCategory = ref(1)
// 搜索关键词
const searchKey = ref('')
// 筛选当前分类的项目(含搜索)
const currentProjectList = computed(() => {
let list = projectList.value.filter(item => item.categoryId === activeCategory.value)
if (searchKey.value) {
list = list.filter(item => item.name.includes(searchKey.value))
}
return list
})
// 选择分类
const selectCategory = (item) => {
activeCategory.value = item.id
}
// 选择项目并跳转到维修项目填写页
const selectProject = (item) => {
// 获取分类名称
const categoryName = categoryList.value.find(c => c.id === item.categoryId)?.name
// 跳转并传参
uni.navigateTo({
url: `/pageSubPack/online-repair/publicRepair?category=${categoryName}&project=${item.name}`
})
}
</script>
<style scoped>
.select-container {
padding: 10px;
height: 100vh;
box-sizing: border-box;
}
.search-bar {
display: flex;
align-items: center;
padding: 8px 10px;
background: #f5f5f5;
border-radius: 6px;
margin-bottom: 10px;
}
.search-bar input {
flex: 1;
font-size: 14px;
}
.category-wrap {
display: flex;
height: calc(100% - 50px);
}
.category-left {
width: 120px;
height: 100%;
background: #f8f8f8;
border-radius: 6px 0 0 6px;
}
.category-item {
padding: 15px 10px;
text-align: center;
font-size: 14px;
border-bottom: 1px solid #eee;
}
.category-item.active {
background: #fff;
color: #007aff;
font-weight: bold;
}
.category-right {
flex: 1;
height: 100%;
background: #fff;
border-radius: 0 6px 6px 0;
padding: 10px;
}
.project-item {
padding: 12px 10px;
font-size: 14px;
border-bottom: 1px solid #eee;
}
.project-item:active {
background: #f5f5f5;
}
</style>

View File

@@ -0,0 +1,114 @@
<template>
<view class="house-container">
<!-- 房屋列表 -->
<view class="house-list">
<view
class="house-item"
:class="{ active: selectedHouse.id === item.id }"
@click="selectHouse(item)"
v-for="item in houseList"
:key="item.id"
>
<image src="http://47.104.199.163:9090/images/repair/house.png" style="width: 60rpx;height: 60rpx;"></image>
<view class="house-name">
{{ item.community }}
<view class="house-detail">{{ item.address }}</view>
</view>
<uni-icons type="checkbox-filled" size="20" color="#007aff" v-if="selectedHouse.id === item.id"></uni-icons>
</view>
</view>
<!-- 确认按钮 -->
<button class="confirm-btn" @click="confirmSelect">确认</button>
</view>
</template>
<script setup>
import { ref } from 'vue'
// import { uniShowToast, uniNavigateBack } from '@dcloudio/uni-app'
// 房屋列表数据
const houseList = ref([
{ id: 1, community: '桂花园(别墅)', address: '2号楼2幢' },
{ id: 2, community: '安泰翡翠城', address: '11号楼5单元1801' },
{ id: 3, community: '碧桂园新城·时代之光', address: '3号楼' }
])
// 选中的房屋
const selectedHouse = ref(houseList.value[0]) // 默认选中第一个
// 选择房屋
const selectHouse = (item) => {
selectedHouse.value = item
}
// 确认选择并返回
const confirmSelect = () => {
if (!selectedHouse.value) {
uni.showToast({ title: '请选择房屋', icon: 'none' })
return
}
// 把选中的房屋信息传给表单页通过uni.$emit
uni.$emit('selectHouse', {
name: `${selectedHouse.value.community} ${selectedHouse.value.address}`
})
// 返回上一页
uni.navigateBack({ delta: 1 })
}
</script>
<style scoped>
.house-container {
padding: 15px;
height: 100vh;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: space-between;
background: #fff;
}
.house-list {
flex: 1;
}
.house-item {
display: flex;
justify-content: flex-start;
align-items: center;
padding: 15px 10px;
border: 1px solid #eee;
border-radius: 8px;
margin-bottom: 10px;
}
.house-item.active {
border-color: #007aff;
background: #f0f8ff;
}
.house-name {
font-size: 16px;
color: #333;
font-weight: 500;
display: flex;
flex-direction: column;
align-items: flex-start;
margin-left: 30rpx;
width: 80%;
}
.house-detail {
font-size: 12px;
color: #666;
margin-top: 4px;
}
.confirm-btn {
width: 100%;
height: 44px;
line-height: 44px;
background: #007aff;
color: #fff;
border-radius: 8px;
font-size: 16px;
margin-top: 20px;
}
</style>