2025-08-12 18:42:56 +08:00

469 lines
12 KiB
Vue
Raw Permalink 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="header">
<image class="avatar" :src="`${IMAGE_BASE_URL}`+detailObj.customerPortrait|| '/static/index/nav.png'" mode="aspectFill"></image>
<text class="username">{{detailObj.customerName || '未知用户'}}</text>
</view>
<!-- 图片区域 - 编辑状态下可修改 -->
<view class="image-area">
<template v-if="!isEditing">
<template v-if="detailObj.images && detailObj.images.length > 0">
<image
class="work-order-img"
:src="`${IMAGE_BASE_URL}${detailObj.images[0]}`"
mode="aspectFit"
/>
</template>
<view class="empty-placeholder" v-else>
<u-icon name="photo" size="48" color="#c0c4cc"></u-icon>
<text class="empty-text">暂无图片</text>
</view>
</template>
<template v-else>
<image
class="work-order-img"
:src="tempImagePath || (detailObj.images && detailObj.images[0] ? `${IMAGE_BASE_URL}${detailObj.images[0]}` : '')"
mode="aspectFit"
v-if="tempImagePath || (detailObj.images && detailObj.images.length > 0)"
/>
<view class="empty-placeholder" v-else>
<u-icon name="photo" size="48" color="#c0c4cc"></u-icon>
<text class="empty-text">暂无图片</text>
</view>
<view class="upload-btn" @click="uploadImage">
<u-icon name="camera" size="40" color="#fff"></u-icon>
<text class="upload-text">{{tempImagePath ? '更换图片' : '上传图片'}}</text>
</view>
</template>
</view>
<!-- 标题与日期 -->
<view class="title-area" :class="{editing: isEditing}">
<text class="title" v-if="!isEditing">{{detailObj.title || '无标题'}}</text>
<input class="title-input" v-model="editLabel" v-else placeholder="请输入标题" />
<text class="date">{{formatTime(detailObj.createdAt,"YYYY-MM-DD") || '未知日期'}}</text>
</view>
<!-- 工单详情内容 - 编辑状态下可修改 -->
<view class="content" :class="{editing: isEditing}">
<view class="label">
<text>求助内容</text>
<text v-if="!isEditing" class="content-text">{{detailObj.content || '无内容描述'}}</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 ? 'primary' : 'primary'"
class="action-btn"
:loading="isLoading"
:custom-style="btnStyle"
>
{{isEditing ? '保存修改' : '修改'}}
</u-button>
<u-button
@click="handleWithdraw"
:type="isEditing ? 'error' : 'error'"
class="action-btn"
:custom-style="btnStyle"
>
{{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, // 加载状态
tempImagePath: '', // 临时存储上传的图片路径
// 编辑状态下的临时数据
editLabel: '',
editTitle: '',
detailObj: {
images: [], // 确保images数组初始化
customerPortrait: '',
customerName: '',
createdAt: '',
title: '',
content: '',
id: ''
},
btnStyle: {
width: '45%',
height: '80rpx'
}
};
},
mounted() {
let obj = uni.getStorageSync("Detail") || {};
this.detailObj = {
...obj // 覆盖默认值
};
},
methods: {
// 上传图片
uploadImage() {
uni.chooseImage({
count: 1,
success: (res) => {
this.tempImagePath = res.tempFilePaths[0];
uni.showToast({
title: '图片已选择',
icon: 'success'
});
},
fail: (err) => {
console.error('选择图片失败:', err);
uni.showToast({
title: '选择图片失败',
icon: 'none'
});
}
});
},
// 上传图片到服务器
async uploadImageToServer(filePath) {
try {
const res = await new Promise((resolve, reject) => {
uni.uploadFile({
url: `${IMAGE_BASE_URL}/api/v1/upload`,
filePath: filePath,
name: 'file',
header: {
'Authorization': `Bearer ${uni.getStorageSync('token')}`
},
success: (uploadRes) => {
try {
const res = JSON.parse(uploadRes.data);
resolve(res);
} catch (e) {
reject(new Error('解析响应失败'));
}
},
fail: (err) => {
reject(new Error('上传失败: ' + JSON.stringify(err)));
}
});
});
if (res && res.success) {
return res.data;
} else {
throw new Error(res.message || '上传失败');
}
} catch (error) {
console.error('上传图片失败:', error);
uni.showToast({
title: '上传图片失败',
icon: 'none'
});
throw error;
}
},
// 修改/保存操作
async handleModify() {
if (!this.isEditing) {
// 进入编辑模式
this.editLabel = this.detailObj.title || '';
this.editTitle = this.detailObj.content || '';
this.isEditing = true;
} else {
// 保存修改
this.isLoading = false;
try {
let images = [...(this.detailObj.images || [])];
// 如果有新图片上传
if (this.tempImagePath) {
const uploadedPath = await this.uploadImageToServer(this.tempImagePath);
images = [uploadedPath];
}
const requestData = {
content: this.editTitle,
title: this.editLabel,
images: images,
status: 1
};
const res = await put(`/api/v1/app_auth/reciprocities/${this.detailObj.id}`, requestData);
if (res && res.success) {
this.detailObj = {
...this.detailObj,
...requestData
};
uni.showToast({
title: '修改成功',
icon: 'success'
});
uni.reLaunch({
url: '/pages/mySeekHelp/index'
});
this.isEditing = false;
this.tempImagePath = '';
} else {
throw new Error(res.message || '修改失败');
}
} catch (error) {
console.error('保存失败:', error);
uni.showToast({
title: error.message || '保存失败,请重试',
icon: 'none'
});
} finally {
this.isLoading = false;
}
}
},
// 撤回/取消操作
async handleWithdraw() {
if (this.isEditing) {
// 取消编辑
this.isEditing = false;
this.tempImagePath = '';
} else {
// 撤回操作
try {
this.isLoading = false;
const res = await put(`/api/v1/app_auth/reciprocities/${this.detailObj.id}`, {status: 98});
if (res && res.success) {
uni.showToast({
title: '撤回成功',
icon: 'success'
});
uni.reLaunch({
url: '/pages/mySeekHelp/index'
});
} else {
throw new Error(res.message || '撤回失败');
}
} catch (error) {
console.error('撤回失败:', error);
uni.showToast({
title: error.message || '撤回失败',
icon: 'none'
});
} finally {
this.isLoading = false;
}
}
}
}
};
</script>
<style lang="scss" scoped>
// 定义变量
$primary-color: #007AFF;
$success-color: #4cd964;
$warning-color: #FF9500;
$error-color: #dd524d;
$text-color: #333;
$subtext-color: #999;
$border-radius: 12rpx;
$padding-base: 20rpx;
$font-base: 32rpx;
$shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
.work-order-detail {
padding: $padding-base;
width: 92%;
margin: 20rpx auto;
border-radius: $border-radius;
box-shadow: $shadow;
background-color: #fff;
.header {
display: flex;
align-items: center;
margin-bottom: $padding-base;
padding-bottom: $padding-base;
border-bottom: 1rpx solid #f5f5f5;
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
margin-right: 20rpx;
background-color: #f5f5f5;
}
.username {
font-size: $font-base;
font-weight: bold;
color: $text-color;
}
}
.image-area {
position: relative;
margin-bottom: $padding-base;
height: 400rpx;
background-color: #f9f9f9;
border-radius: $border-radius;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
.work-order-img {
width: 100%;
height: 100%;
object-fit: contain;
}
.empty-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #c0c4cc;
width: 100%;
height: 100%;
.empty-text {
font-size: 28rpx;
margin-top: 16rpx;
color: #c0c4cc;
}
}
.upload-btn {
position: absolute;
bottom: 30rpx;
right: 30rpx;
background: rgba(0, 0, 0, 0.6);
padding: 12rpx 24rpx;
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
.upload-text {
color: #fff;
font-size: 26rpx;
margin-left: 10rpx;
}
}
}
.title-area {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: $padding-base;
padding-bottom: $padding-base;
border-bottom: 1rpx solid #f5f5f5;
&.editing {
border-bottom: 1rpx solid #eaeaea;
}
.title {
font-size: $font-base + 4rpx;
font-weight: bold;
color: $text-color;
flex: 1;
}
.title-input {
font-size: $font-base + 4rpx;
font-weight: bold;
color: $text-color;
flex: 1;
padding: 10rpx;
background: #f8f8f8;
border-radius: 8rpx;
border: 1rpx solid #eaeaea;
}
.date {
font-size: $font-base - 4rpx;
color: $subtext-color;
margin-left: 20rpx;
}
}
.content {
margin-bottom: $padding-base * 2;
font-size: $font-base;
&.editing {
padding: 10rpx;
background: #fafafa;
border-radius: $border-radius;
}
.label {
display: flex;
flex-direction: column;
text {
font-weight: bold;
margin-bottom: 10rpx;
}
.content-text {
font-weight: normal;
color: #666;
line-height: 1.6;
}
}
.edit-textarea {
width: 100%;
padding: 16rpx;
background: #fff;
border-radius: 8rpx;
border: 1rpx solid #eaeaea;
min-height: 200rpx;
font-size: $font-base;
line-height: 1.5;
}
}
.btn-group {
display: flex;
justify-content: space-between;
margin-top: 40rpx;
gap: 20rpx;
.action-btn {
flex: 1;
border-radius: 50rpx;
font-size: $font-base;
font-weight: bold;
}
}
}
</style>