2025-07-22 14:11:42 +08:00

601 lines
13 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="image-area card">
<view class="img-container">
<image
class="work-order-img"
:src="!isEditing ? `${IMAGE_BASE_URL}${detailObj.images[0] || ''}` : workOrderImg"
mode="widthFix"
:lazy-load="true"
></image>
<view class="upload-btn" @click="uploadImage" v-if="isEditing">
<u-icon name="camera" size="28" color="#fff"></u-icon>
<text class="upload-text">更换图片</text>
</view>
</view>
</view>
<!-- 标题与日期 -->
<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">
<!-- <u-icon name="map-marker" size="24" color="#409EFF"></u-icon> -->
<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">
<!-- <u-icon name="user" size="24" color="#409EFF"></u-icon> -->
<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">
<!-- <u-icon name="phone" size="24" color="#409EFF"></u-icon> -->
<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">
<!-- <u-icon name="file-text" size="24" color="#409EFF"></u-icon> -->
<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="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, // 加载状态
workOrderImg: '', // 编辑时的图片
// 编辑状态下的临时数据
editLabel: '',
editAddress: '',
editTitle: '',
editContactName: '', // 新增:联系人
editContactPhone: '', // 新增:联系电话
detailObj: {},
tempImagePath: '' // 临时存储上传的图片路径
};
},
mounted() {
let obj = uni.getStorageSync("Detail");
this.detailObj = {...obj};
this.workOrderImg = `${IMAGE_BASE_URL}${this.detailObj.images[0]}`;
// 初始化新增字段
this.editContactName = this.detailObj.concatName || '';
this.editContactPhone = this.detailObj.customerPhone || '';
},
methods: {
// 上传图片到服务器
uploadImage() {
uni.chooseImage({
count: 1,
success: async (res) => {
const tempFilePaths = res.tempFilePaths[0];
this.tempImagePath = tempFilePaths;
this.workOrderImg = tempFilePaths;
uni.showToast({
title: '图片已更换',
icon: 'success'
});
}
});
},
// 上传图片到服务器
async uploadImageToServer(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.tempImagePath) {
const uploadedPath = await this.uploadImageToServer(this.tempImagePath);
images = [uploadedPath]; // 替换为新的图片
}
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.tempImagePath = ''; // 清空临时图片路径
} 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.tempImagePath = '';
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;
}
}
/* 页面容器 */
.work-order-detail {
background-color: $bg-color;
min-height: 100vh;
padding: 0 30rpx 40rpx;
}
/* 导航栏 */
.navbar {
@include flex-center;
height: 100rpx;
width: 100%;
padding: 0 10rpx;
background: $card-bg;
border-bottom: 1px solid $border-color;
position: sticky;
top: 0;
z-index: 10;
margin-bottom: 24rpx;
.nav-title {
font-size: 36rpx;
color: $text-color;
font-weight: 500;
flex: 1;
text-align: center;
margin-left: -36rpx; // 抵消图标宽度
}
}
/* 头部用户信息 */
.header {
@include card;
display: flex;
align-items: center;
padding: 24rpx 30rpx;
.avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background-color: $bg-color;
border: 2rpx solid #F0F2F5;
}
.user-info {
margin-left: 24rpx;
flex: 1;
}
.user-name-wrap {
@include flex-center;
margin-bottom: 8rpx;
}
.username {
font-size: 34rpx;
color: $text-color;
font-weight: 500;
}
.status-tag {
margin-left: 12rpx;
height: 40rpx;
line-height: 40rpx;
}
.contact-info {
@include flex-center;
font-size: 28rpx;
}
.info-icon {
margin-right: 6rpx;
}
.user-phone {
color: $subtext-color;
}
}
/* 图片区域优化 */
.image-area {
@include card;
padding: 24rpx;
.img-container {
position: relative;
width: 100%;
border-radius: 8rpx;
overflow: hidden;
background-color: $bg-color;
}
.work-order-img {
width: 100%;
border-radius: 8rpx;
transition: $transition;
// 加载占位效果
&:empty {
height: 300rpx;
background-color: $bg-color;
display: flex;
align-items: center;
justify-content: center;
color: $light-text;
}
}
.upload-btn {
position: absolute;
right: 16rpx;
bottom: 16rpx;
background: rgba(0, 0, 0, 0.5);
padding: 8rpx 16rpx;
border-radius: 24rpx;
@include flex-center;
transition: $transition;
cursor: pointer;
&:hover {
background: rgba(0, 0, 0, 0.7);
}
.upload-text {
color: #fff;
font-size: 26rpx;
margin-left: 6rpx;
}
}
}
/* 标题区域优化 */
.title-area {
@include card;
display: flex;
flex-direction: column;
gap: 16rpx;
.title-wrap {
@include flex-center;
width: 100%;
}
.title-icon {
margin-right: 12rpx;
flex-shrink: 0;
}
.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-icon {
margin-right: 6rpx;
}
.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;
}
.label-name u-icon {
margin-right: 8rpx;
}
& > 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;
}
}
}
/* 适配小屏幕 */
@media (max-width: 375px) {
$font-base: 28rpx;
.header {
padding: 18rpx;
.avatar {
width: 80rpx;
height: 80rpx;
}
}
.btn-group {
padding: 20rpx 5rpx;
.action-btn {
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
}
}
}
</style>