qiuyuan 9f052c8cc8 1
2025-08-06 18:05:09 +08:00

622 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="work-order-detail">
<!-- 标题与日期 -->
<view class="title-area card">
<view class="title-wrap">
<text class="title" :class="{empty: !detailObj.label}">{{detailObj.label || '未设置标题'}}</text>
</view>
<view class="date-wrap">
<text class="date">{{formatTime(detailObj.createdAt,"YYYY-MM-DD") || '未知时间'}}</text>
</view>
</view>
<!-- 工单详情内容 - 编辑状态下可修改 -->
<view class="content card">
<view class="label">
<view class="label-name">
<text>工单地点</text>
</view>
<text v-if="!isEditing" style="font-weight: normal;" :class="{empty: !detailObj.address}">{{detailObj.address || '未填写'}}</text>
<input class="edit-input" v-model="editAddress" v-else placeholder="请输入工单地点" />
</view>
<view class="label">
<view class="label-name">
<text>联系人:</text>
</view>
<text v-if="!isEditing" style="font-weight: normal;" :class="{empty: !detailObj.concatName}">{{detailObj.concatName || '未填写'}}</text>
<input class="edit-input" v-model="editContactName" v-else placeholder="请输入联系人" />
</view>
<view class="label">
<view class="label-name">
<text>联系电话:</text>
</view>
<text v-if="!isEditing" style="font-weight: normal;" :class="{empty: !detailObj.customerPhone}">{{detailObj.customerPhone || '未填写'}}</text>
<input class="edit-input" v-model="editContactPhone" v-else placeholder="请输入联系电话" type="number" />
</view>
<view class="label label-content">
<view class="label-name">
<text>工单内容:</text>
</view>
<text v-if="!isEditing" style="font-weight: normal; white-space: pre-line;" :class="{empty: !detailObj.title}">{{detailObj.title || '未填写'}}</text>
<textarea
class="edit-textarea"
v-model="editTitle"
v-else
placeholder="请输入工单详细内容"
auto-height
/>
</view>
</view>
<!-- 图片区域 - 支持多张展示和编辑 -->
<view class="image-area card">
<view class="img-container">
<!-- 非编辑状态显示服务器图片 -->
<scroll-view
class="image-scroll"
scroll-x="true"
v-if="!isEditing && detailObj.images && detailObj.images.length > 0"
>
<view class="image-list">
<image
class="work-order-img"
v-for="(img, index) in detailObj.images"
:key="index"
:src="`${IMAGE_BASE_URL}${img}`"
mode="aspectFill"
lazy-load="true"
@click="previewImage(index)"
></image>
</view>
</scroll-view>
<!-- 编辑状态显示本地选择的图片 -->
<scroll-view
class="image-scroll"
scroll-x="true"
v-if="isEditing && localImages.length > 0"
>
<view class="image-list">
<view class="image-item" v-for="(img, index) in localImages" :key="index">
<image
class="work-order-img"
:src="img"
mode="aspectFill"
lazy-load="true"
></image>
<view class="delete-btn" @click="removeImage(index)">
<u-icon name="close" size="20" color="#fff"></u-icon>
</view>
</view>
</view>
</scroll-view>
<!-- 没有图片时的占位 -->
<view class="empty-placeholder" v-if="(!detailObj.images || detailObj.images.length === 0) && !isEditing">
<u-icon name="photo" size="48" color="#c0c4cc"></u-icon>
<text class="empty-text">暂无图片</text>
</view>
<!-- 编辑状态的上传按钮 -->
<view class="upload-btn-container" v-if="isEditing">
<view class="upload-btn" @click="uploadImage">
<u-icon name="camera" size="28" color="#fff"></u-icon>
<text class="upload-text">{{ localImages.length > 0 ? '添加更多' : '上传图片' }}</text>
</view>
<text class="upload-tip" v-if="localImages.length > 0">最多可上传9张</text>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="btn-group">
<u-button
@click="handleModify"
:type="isEditing ? 'success' : 'primary'"
class="action-btn primary-btn"
:loading="isLoading"
shape="circle"
>
{{isEditing ? '保存修改' : '修改'}}
</u-button>
<u-button
@click="handleWithdraw"
:type="isEditing ? 'default' : 'warning'"
class="action-btn"
shape="circle"
>
{{isEditing ? '取消编辑' : '撤回'}}
</u-button>
</view>
</view>
</template>
<script>
import { get, put } from '@/utils/request';
import { IMAGE_BASE_URL, BASE_URL } from '@/utils/config';
import { formatTime } from '@/utils/timeFormat';
export default {
data() {
return {
IMAGE_BASE_URL,
BASE_URL,
formatTime,
isEditing: false, // 编辑状态
isLoading: false, // 加载状态
localImages: [], // 本地选择的图片数组
// 编辑状态下的临时数据
editLabel: '',
editAddress: '',
editTitle: '',
editContactName: '',
editContactPhone: '',
detailObj: {},
tempImagePaths: [] // 临时存储上传的图片路径
};
},
mounted() {
let obj = uni.getStorageSync("Detail");
this.detailObj = {...obj};
// 初始化本地图片数组
if (this.detailObj.images && this.detailObj.images.length > 0) {
this.localImages = this.detailObj.images.map(img => `${IMAGE_BASE_URL}${img}`);
}
// 初始化新增字段
this.editContactName = this.detailObj.concatName || '';
this.editContactPhone = this.detailObj.customerPhone || '';
},
methods: {
// 预览图片
previewImage(index) {
uni.previewImage({
current: index,
urls: this.detailObj.images.map(img => `${IMAGE_BASE_URL}${img}`)
});
},
// 上传图片
uploadImage() {
const maxCount = 9 - this.localImages.length;
if (maxCount <= 0) {
uni.showToast({
title: '最多只能上传9张图片',
icon: 'none'
});
return;
}
uni.chooseImage({
count: maxCount,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.tempImagePaths = [...this.tempImagePaths, ...res.tempFilePaths];
this.localImages = [...this.localImages, ...res.tempFilePaths];
}
});
},
// 删除图片
removeImage(index) {
this.localImages.splice(index, 1);
// 如果是新上传的图片也从tempImagePaths中移除
if (index >= this.localImages.length - this.tempImagePaths.length) {
this.tempImagePaths.splice(index - (this.localImages.length - this.tempImagePaths.length), 1);
}
},
// 上传图片到服务器
async uploadImagesToServer() {
const uploadedPaths = [];
for (const filePath of this.tempImagePaths) {
try {
const path = await this.uploadSingleImage(filePath);
uploadedPaths.push(path);
} catch (error) {
console.error('图片上传失败:', error);
throw error;
}
}
return uploadedPaths;
},
// 上传单张图片
uploadSingleImage(filePath) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: `${BASE_URL}/api/v1/upload`,
filePath: filePath,
name: 'file',
header: {
'Authorization': `Bearer ${uni.getStorageSync('token')}`
},
success: (uploadRes) => {
try {
const res = JSON.parse(uploadRes.data);
if (res && res.success) {
resolve(res.data);
} else {
reject(new Error(res.message || '上传失败'));
}
} catch (e) {
reject(new Error('解析响应失败'));
}
},
fail: (err) => {
reject(new Error('上传失败: ' + JSON.stringify(err)));
}
});
});
},
// 修改/保存操作
async handleModify() {
if (!this.isEditing) {
// 进入编辑模式
this.editLabel = this.detailObj.label;
this.editAddress = this.detailObj.address;
this.editTitle = this.detailObj.title;
this.editContactName = this.detailObj.concatName;
this.editContactPhone = this.detailObj.customerPhone;
this.isEditing = true;
} else {
// 保存修改
this.isLoading = true;
try {
let images = this.detailObj.images;
// 如果有新图片上传
if (this.tempImagePaths.length > 0) {
const uploadedPaths = await this.uploadImagesToServer();
// 合并原有图片和新上传的图片
const originalPaths = this.localImages
.filter(img => img.startsWith(IMAGE_BASE_URL))
.map(img => img.replace(IMAGE_BASE_URL, ''));
images = [...originalPaths, ...uploadedPaths];
} else if (this.localImages.length === 0) {
// 用户删除了所有图片
images = [];
}
const requestData = {
label: this.editLabel,
address: this.editAddress,
title: this.editTitle,
images: images,
status: 1,
concatName: this.editContactName,
customerPhone: this.editContactPhone
};
const res = await put(`/api/v1/app_auth/work-order/${this.detailObj.id}`, requestData);
if (res && res.success) {
// 更新本地数据
this.detailObj = {
...this.detailObj,
...requestData
};
uni.showToast({
title: '修改成功',
icon: 'success'
});
this.isEditing = false;
this.tempImagePaths = []; // 清空临时图片路径
} else {
uni.showToast({
title: res.message || '修改失败',
icon: 'none'
});
}
} catch (error) {
console.error('保存失败:', error);
uni.showToast({
title: '保存失败,请重试',
icon: 'none'
});
} finally {
this.isLoading = false;
}
}
},
// 撤回/取消操作
async handleWithdraw() {
if (this.isEditing) {
// 取消编辑状态,恢复原始数据
this.isEditing = false;
this.tempImagePaths = [];
if (this.detailObj.images && this.detailObj.images.length > 0) {
this.localImages = this.detailObj.images.map(img => `${IMAGE_BASE_URL}${img}`);
} else {
this.localImages = [];
}
return;
}
// 撤回工单
const res = await put(`/api/v1/app_auth/work-order/${this.detailObj.id}`, {status:98});
if (res && res.success) {
uni.showToast({
title: '撤回成功',
icon: 'success'
});
} else {
uni.showToast({
title: res.message || '撤回失败',
icon: 'none'
});
}
}
}
};
</script>
<style lang="scss" scoped>
/* 基础变量优化 */
$primary-color: #409EFF;
$success-color: #67C23A;
$warning-color: #F56C6C;
$text-color: #303133;
$subtext-color: #606266;
$light-text: #909399;
$border-color: #E4E7ED;
$bg-color: #F5F7FA;
$card-bg: #FFFFFF;
$border-radius: 12rpx;
$shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
$transition: all 0.2s ease;
/* 工具类 */
@mixin flex-center {
display: flex;
align-items: center;
}
@mixin card {
background: $card-bg;
border-radius: $border-radius;
box-shadow: $shadow;
padding: 30rpx;
margin-bottom: 24rpx;
transition: $transition;
&:last-child {
margin-bottom: 0;
}
}
/* 图片区域优化 */
.image-area {
@include card;
padding: 24rpx;
.img-container {
position: relative;
width: 100%;
border-radius: 8rpx;
overflow: hidden;
background-color: $bg-color;
min-height: 200rpx;
}
.image-scroll {
width: 100%;
white-space: nowrap;
}
.image-list {
display: inline-flex;
gap: 16rpx;
}
.image-item {
position: relative;
width: 200rpx;
height: 200rpx;
border-radius: 8rpx;
overflow: hidden;
}
.work-order-img {
width: 200rpx;
height: 200rpx;
border-radius: 8rpx;
}
.delete-btn {
position: absolute;
right: 8rpx;
top: 8rpx;
width: 36rpx;
height: 36rpx;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
@include flex-center;
justify-content: center;
}
.empty-placeholder {
width: 100%;
height: 300rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #f5f7fa;
border-radius: 8rpx;
.empty-text {
margin-top: 16rpx;
font-size: 28rpx;
color: #909399;
}
}
.upload-btn-container {
margin-top: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 10rpx;
}
.upload-btn {
background: $primary-color;
padding: 12rpx 24rpx;
border-radius: 24rpx;
@include flex-center;
transition: $transition;
.upload-text {
color: #fff;
font-size: 26rpx;
margin-left: 6rpx;
}
}
.upload-tip {
font-size: 24rpx;
color: $light-text;
}
}
/* 其他样式保持不变 */
.work-order-detail {
background-color: $bg-color;
min-height: 100vh;
padding: 0 30rpx 40rpx;
}
.title-area {
@include card;
display: flex;
flex-direction: column;
gap: 16rpx;
.title-wrap {
@include flex-center;
width: 100%;
}
.title {
font-size: 34rpx;
color: $text-color;
font-weight: 500;
line-height: 1.5;
}
.date-wrap {
@include flex-center;
align-self: flex-end;
padding-top: 4rpx;
}
.date {
font-size: 26rpx;
color: $light-text;
}
}
.content {
@include card;
.label {
display: flex;
width: 100%;
margin-bottom: 28rpx;
align-items: flex-start;
padding-bottom: 28rpx;
border-bottom:1rpx dashed $border-color;
&:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.label-name {
@include flex-center;
min-width: 160rpx;
font-weight: 500;
color: $subtext-color;
font-size: 28rpx;
flex-shrink: 0;
}
& > text {
font-size: 28rpx;
color: $text-color;
line-height: 1.6;
flex: 1;
}
}
.label-content {
align-items: flex-start;
& > text {
white-space: pre-line;
word-break: break-all;
}
}
.edit-input, .edit-textarea {
flex: 1;
padding: 12rpx 16rpx;
background: $bg-color;
border-radius: 8rpx;
border: 1px solid $border-color;
font-size: 28rpx;
color: $text-color;
line-height: 1.5;
transition: $transition;
&:focus {
border-color: $primary-color;
background: #fff;
outline: none;
box-shadow: 0 0 0 2rpx rgba(64, 158, 255, 0.2);
}
}
.edit-textarea {
min-height: 180rpx;
resize: none;
}
.empty {
color: $light-text;
font-style: italic;
}
}
.btn-group {
display: flex;
gap: 24rpx;
padding: 30rpx 10rpx 20rpx;
background-color: transparent;
.action-btn {
flex: 1;
height: 90rpx;
line-height: 90rpx;
font-size: 30rpx;
font-weight: 500;
border-radius: 45rpx;
transition: $transition;
}
.primary-btn {
background-color: $primary-color;
&:active {
background-color: #3A8EE6;
}
}
}
</style>