This commit is contained in:
qiuyuan 2025-08-07 12:22:19 +08:00
parent 0adc144dc4
commit 42b6d2db33
6 changed files with 840 additions and 704 deletions

View File

@ -34,7 +34,7 @@
<view class="info-item"> <view class="info-item">
<u-icon name="clock" size="26" color="#5b9cf8"></u-icon> <u-icon name="clock" size="26" color="#5b9cf8"></u-icon>
<view class="info-text"><text class="info-label">活动时间</text>{{ activityInfo.openAt }}</view> <view class="info-text"><text class="info-label">开始时间</text>{{formatTime(activityInfo.startAt,"YYYY-MM-DD HH:mm:ss") }}</view>
</view> </view>
<view class="info-item"> <view class="info-item">

View File

@ -1,282 +1,371 @@
<template> <template>
<view class="container"> <view class="container">
<view class="content"> <view class="content">
<view class="content_wapper"> <view class="content_wapper">
<!-- 添加标题 --> <!-- 添加标题 -->
<view class="input-section"> <view class="input-section">
<u-input placeholder="添加标题" v-model="title" border></u-input> <u-input placeholder="添加标题" v-model="title" border></u-input>
</view> </view>
<!-- 输入求助内容 --> <!-- 输入求助内容 -->
<view class="textarea-section"> <view class="textarea-section">
<u--textarea placeholder="请输入求助内容..." v-model="content" border height="300"></u--textarea> <u--textarea placeholder="请输入求助内容..." v-model="content" border height="300"></u--textarea>
</view> </view>
<!-- 图片上传 --> <!-- 图片上传 - 修改后的上传组件 -->
<view class="upload-section"> <view class="upload-section">
<u-upload <view class="label">
@afterRead="afterRead" <u-icon name="photo" color="#3B8CFF" size="38"></u-icon>
@delete="deletePic" 上传照片
:fileList="fileList" </view>
:maxCount="9" <view class="upload-area">
:previewFullImage="true" <view class="upload-list">
:accept="'image/*'" <view class="upload-item" v-for="(item, index) in fileList" :key="index">
:capture="['album', 'camera']" <image :src="item.url" mode="aspectFill" @click="previewImage(index)"></image>
width="220" <view class="delete-btn" @click="handleDelete(index)">
height="220"> <u-icon name="close" color="#fff" size="24"></u-icon>
<view class="upload-btn" v-if="fileList.length < 1"> </view>
<u-icon name="plus" size="40" color="#666"></u-icon> </view>
</view> <view class="upload-btn" @click="showUploadAction" v-if="fileList.length < 9">
</u-upload> <u-icon name="plus" size="40" color="#c0c4cc"></u-icon>
</view> </view>
</view>
<!-- 发布按钮 --> </view>
<view class="publish-section"> <text class="note">照片支持分批上传但最大数量为9</text>
<u-button type="primary" text="立即发布" @click="publish"></u-button> </view>
</view>
</view> <!-- 发布按钮 -->
<view class="publish-section">
</view> <u-button type="primary" text="立即发布" @click="publish"></u-button>
<Footer></Footer> </view>
</view> </view>
</view>
</template> <Footer></Footer>
</view>
<script> </template>
import Footer from '@/components/footer_common.vue';
import { <script>
get, import Footer from '@/components/footer_common.vue';
post import {
} from '@/utils/request'; get,
import { post
IMAGE_BASE_URL, } from '@/utils/request';
BASE_URL import {
} from '@/utils/config'; IMAGE_BASE_URL,
export default { BASE_URL
components: { } from '@/utils/config';
Footer export default {
}, components: {
data() { Footer
return { },
title: '', data() {
content: '', return {
fileList: [], title: '',
uploadedImageUrls: [] content: '',
}; fileList: [],
}, uploadedImageUrls: []
methods: { };
handleBack() { },
uni.navigateBack({ methods: {
delta: 1 // handleBack() {
}); uni.navigateBack({
}, delta: 1 //
async afterRead(event) { });
// },
const files = event.file; //
const uploadFiles = Array.isArray(files) ? files : [files]; //
showUploadAction() {
uni.showActionSheet({
itemList: ['拍照', '从相册选择'],
success: (res) => {
if (res.tapIndex === 0) {
this.chooseMedia('camera');
} else {
this.chooseMedia('album');
}
}
});
},
//
async chooseMedia(sourceType) {
try {
// 1.
const res = await new Promise((resolve, reject) => {
uni.chooseImage({
count: 9 - this.fileList.length,
sourceType: [sourceType === 'camera' ? 'camera' : 'album'],
success: resolve,
fail: reject
});
});
// - // 2.
for (const file of uploadFiles) { const newFiles = res.tempFilePaths.map(url => ({
// 1 url,
const fileExt = file.url.split('.').pop().toLowerCase(); status: 'uploading'
const allowedExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']; }));
this.fileList = [...this.fileList, ...newFiles];
// 2MIME
const isImage = file.type?.startsWith('image/') || // 3.
allowedExts.includes(fileExt); for (const file of newFiles) {
await this.uploadFile(file);
if (!isImage) {
uni.showToast({
title: '只能上传图片文件(JPG/PNG等)',
icon: 'none',
duration: 2000
});
return;
}
} }
uni.showLoading({ title: '上传中...' }); } catch (err) {
console.error('选择图片失败:', err);
try { uni.showToast({ title: '选择图片失败', icon: 'none' });
// }
const uploadPromises = uploadFiles.map(file => this.uploadAvatar(file.url)); },
const uploadedUrls = await Promise.all(uploadPromises);
//
// URL async uploadFile(file) {
this.uploadedImageUrls = [...this.uploadedImageUrls, ...uploadedUrls]; try {
const res = await new Promise((resolve, reject) => {
// uni.uploadFile({
this.fileList = [ url: `${BASE_URL}/api/v1/upload`,
...this.fileList, filePath: file.url,
...uploadFiles.map((file, index) => ({ name: 'file',
url: file.url, header: {
status: 'success', 'Authorization': `Bearer ${uni.getStorageSync('token')}`,
uploadedUrl: uploadedUrls[index] 'Content-Type': 'multipart/form-data'
})) },
]; success: (uploadRes) => {
try {
uni.showToast({ title: '上传成功', icon: 'success' }); const data = JSON.parse(uploadRes.data);
} catch (error) { if (data.success) {
console.error('上传失败:', error); resolve(data.data); // 使URL
uni.showToast({ } else {
title: '上传失败: ' + error.message, reject(data.message || '上传失败');
icon: 'none', }
duration: 2000 } catch (e) {
}); reject('解析响应失败');
} finally { }
uni.hideLoading(); },
fail: (err) => reject(err)
});
});
//
const index = this.fileList.findIndex(f => f.url === file.url);
if (index !== -1) {
this.$set(this.fileList, index, {
...file,
status: 'success',
serverUrl: res // URL
});
//
this.uploadedImageUrls.push(res);
} }
},
deletePic(event) { } catch (err) {
// URL console.error('上传失败:', err);
const deletedFile = this.fileList[event.index]; const index = this.fileList.findIndex(f => f.url === file.url);
this.uploadedImageUrls = this.uploadedImageUrls.filter( if (index !== -1) {
url => url !== deletedFile.uploadedUrl this.$set(this.fileList, index, {
); ...file,
status: 'failed',
// error: err.message || err
this.fileList.splice(event.index, 1); });
}, }
uploadAvatar(filePath) { uni.showToast({
return new Promise((resolve, reject) => { title: `上传失败: ${err.message || err}`,
uni.uploadFile({ icon: 'none'
url: `${BASE_URL}/api/v1/upload`, });
filePath: filePath, }
name: 'file', },
header: {
'Authorization': `Bearer ${uni.getStorageSync('token')}` //
}, previewImage(index) {
success: (uploadRes) => { const images = this.fileList.map(item => item.url);
try {
const res = JSON.parse(uploadRes.data); uni.previewImage({
if (res && res.success) { current: index,
resolve(res.data); urls: images
} else { });
reject(new Error(res.message || '上传失败')); },
}
} catch (e) { //
reject(new Error('解析响应失败')); handleDelete(index) {
} const file = this.fileList[index];
},
fail: (err) => { if (file.serverUrl) {
reject(new Error('上传失败: ' + JSON.stringify(err))); const imageIndex = this.uploadedImageUrls.indexOf(file.serverUrl);
} if (imageIndex !== -1) this.uploadedImageUrls.splice(imageIndex, 1);
}); }
});
}, this.fileList.splice(index, 1);
async publish() { },
if (!this.title.trim()) {
uni.showToast({ async publish() {
title: '请输入标题', if (!this.title.trim()) {
icon: 'none' uni.showToast({
}); title: '请输入标题',
return; icon: 'none'
} });
return;
if (!this.content.trim()) { }
uni.showToast({
title: '请输入内容', if (!this.content.trim()) {
icon: 'none' uni.showToast({
}); title: '请输入内容',
return; icon: 'none'
} });
return;
uni.showLoading({ }
title: '发布中...'
}); uni.showLoading({
title: '发布中...'
try { });
// URL
const postData = { try {
title: this.title, // URL
content: this.content, const postData = {
images: this.uploadedImageUrls.length > 0 ? title: this.title,
this.uploadedImageUrls : content: this.content,
[] // images: this.uploadedImageUrls.length > 0 ?
}; this.uploadedImageUrls :
[] //
// API };
// API
const res = await post('/api/v1/app_auth/reciprocities', postData); const res = await post('/api/v1/app_auth/reciprocities', postData);
if (!res || !res.success) { if (!res || !res.success) {
throw new Error('发布失败'); throw new Error('发布失败');
} }
uni.showToast({ uni.showToast({
title: '发布成功', title: '发布成功',
icon: 'success' icon: 'success'
}); });
// //
setTimeout(() => { setTimeout(() => {
uni.navigateBack(); uni.navigateBack();
}, 1500); }, 1500);
} catch (error) { } catch (error) {
console.error('发布失败:', error); console.error('发布失败:', error);
uni.showToast({ uni.showToast({
title: '发布失败', title: '发布失败',
icon: 'none' icon: 'none'
}); });
} finally { } finally {
uni.hideLoading(); uni.hideLoading();
} }
} }
} }
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
width: 100%;
width: 100%; height: 100vh;
height: 100vh; /* 占满整个视口高度 */
/* 占满整个视口高度 */ opacity: 1;
opacity: 1; background: linear-gradient(0deg, rgba(240, 240, 240, 1) 0%, rgba(255, 250, 250, 0) 100%),
background: linear-gradient(0deg, rgba(240, 240, 240, 1) 0%, rgba(255, 250, 250, 0) 100%), linear-gradient(0deg, rgba(255, 241, 235, 1) 0%, rgba(192, 219, 250, 1) 100%);
linear-gradient(0deg, rgba(255, 241, 235, 1) 0%, rgba(192, 219, 250, 1) 100%); position: relative;
position: relative;
.content {
.content { width: 100%;
width: 100%; position: absolute;
position: absolute; margin-top: 40rpx;
margin-top: 40rpx; border-top-left-radius: 30rpx;
border-top-left-radius: 30rpx; border-top-right-radius: 30rpx;
border-top-right-radius: 30rpx; background-color: rgba(255, 255, 255, 0.65);
background-color: rgba(255, 255, 255, 0.65); padding-bottom: 60rpx;
padding-bottom: 60rpx;
.content_wapper {
.content_wapper { width: 90%;
width: 90%; margin: 0 auto;
margin: 0 auto; }
}
}
}
.input-section {
.input-section { border-bottom: 2rpx solid #C3DCFA;
border-bottom: 2rpx solid #C3DCFA; height: 90rpx;
height: 90rpx; line-height: 90rpx;
line-height: 90rpx; }
}
.input-section,
.input-section, .textarea-section,
.textarea-section, .upload-section {
.upload-section { margin-bottom: 30rpx;
margin-bottom: 30rpx; }
}
.upload-section {
.upload-btn { .label {
width: 220rpx; font-size: 32rpx;
height: 220rpx; color: #333;
display: flex; font-weight: 500;
align-items: center; margin-bottom: 24rpx;
justify-content: center; display: flex;
background-color: #f0f0f0; align-items: center;
border-radius: 10rpx;
} .u-icon {
margin-right: 12rpx;
.publish-section { }
text-align: center; }
margin-top: 100rpx;
} .upload-area {
margin-top: 16rpx;
}
.upload-list {
display: flex;
flex-wrap: wrap;
margin: -5rpx;
.upload-item, .upload-btn {
width: 160rpx;
height: 160rpx;
margin: 5rpx;
position: relative;
background: #f8f8f8;
border-radius: 8rpx;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
image {
width: 100%;
height: 100%;
}
.delete-btn {
position: absolute;
right: 0;
top: 0;
width: 40rpx;
height: 40rpx;
background: rgba(0, 0, 0, 0.5);
border-radius: 0 0 0 8rpx;
display: flex;
justify-content: center;
align-items: center;
}
}
.upload-btn {
border: 1rpx dashed #c0c4cc;
}
}
}
.note {
font-size: 24rpx;
color: #999;
margin-top: 24rpx;
display: block;
}
}
.publish-section {
text-align: center;
margin-top: 100rpx;
}
}
</style> </style>

View File

@ -1,437 +1,464 @@
<template> <template>
<view class="work-order-detail"> <view class="work-order-detail">
<!-- 头部用户信息区域 --> <!-- 头部用户信息区域 -->
<view class="header"> <view class="header">
<image class="avatar" :src="detailObj.customerPortrait" mode="aspectFill"></image> <image class="avatar" :src="`${IMAGE_BASE_URL}`+detailObj.customerPortrait|| '/static/index/nav.png'" mode="aspectFill"></image>
<text class="username">{{detailObj.customerName}}</text> <text class="username">{{detailObj.customerName || '未知用户'}}</text>
</view> </view>
<!-- 图片区域 - 编辑状态下可修改 --> <!-- 图片区域 - 编辑状态下可修改 -->
<view class="image-area"> <view class="image-area">
<image <template v-if="!isEditing">
class="work-order-img" <template v-if="detailObj.images && detailObj.images.length > 0">
:src="`${IMAGE_BASE_URL}${detailObj.images && detailObj.images[0] ? detailObj.images[0] : ''}`" <image
mode="widthFix" class="work-order-img"
v-if="!isEditing && detailObj.images && detailObj.images.length > 0"> :src="`${IMAGE_BASE_URL}${detailObj.images[0]}`"
</image> mode="aspectFit"
<view v-else> />
<image </template>
:src="isEditing ? workOrderImg : `${IMAGE_BASE_URL}${detailObj.images && detailObj.images[0] ? detailObj.images[0] : ''}`" > <view class="empty-placeholder" v-else>
</image> <u-icon name="photo" size="48" color="#c0c4cc"></u-icon>
<view class="upload-btn" @click="uploadImage" v-if="isEditing"> <text class="empty-text">暂无图片</text>
<u-icon name="camera" size="40" color="#fff"></u-icon> </view>
<text class="upload-text">更换图片</text> </template>
</view> <template v-else>
</view> <image
</view> class="work-order-img"
:src="tempImagePath || (detailObj.images && detailObj.images[0] ? `${IMAGE_BASE_URL}${detailObj.images[0]}` : '')"
<!-- 标题与日期 --> mode="aspectFit"
<view class="title-area"> v-if="tempImagePath || (detailObj.images && detailObj.images.length > 0)"
<text class="title" v-if="!isEditing">{{detailObj.title}}</text> />
<input class="title-input" v-model="editLabel" v-else placeholder="请输入标题" /> <view class="empty-placeholder" v-else>
<text class="date">{{formatTime(detailObj.createdAt,"YYYY-MM-DD")}}</text> <u-icon name="photo" size="48" color="#c0c4cc"></u-icon>
</view> <text class="empty-text">暂无图片</text>
</view>
<!-- 工单详情内容 - 编辑状态下可修改 --> <view class="upload-btn" @click="uploadImage">
<view class="content"> <u-icon name="camera" size="40" color="#fff"></u-icon>
<view class="label"> <text class="upload-text">{{tempImagePath ? '更换图片' : '上传图片'}}</text>
<text>求助内容</text> </view>
<text v-if="!isEditing" style="font-weight: normal;">{{detailObj.content}}</text> </template>
<textarea </view>
class="edit-textarea"
v-model="editTitle" <!-- 标题与日期 -->
v-else <view class="title-area" :class="{editing: isEditing}">
placeholder="请输入内容" <text class="title" v-if="!isEditing">{{detailObj.title || '无标题'}}</text>
auto-height <input class="title-input" v-model="editLabel" v-else placeholder="请输入标题" />
/> <text class="date">{{formatTime(detailObj.createdAt,"YYYY-MM-DD") || '未知日期'}}</text>
</view> </view>
</view>
<!-- 工单详情内容 - 编辑状态下可修改 -->
<!-- 操作按钮 --> <view class="content" :class="{editing: isEditing}">
<view class="btn-group"> <view class="label">
<u-button <text>求助内容</text>
@click="handleModify" <text v-if="!isEditing" class="content-text">{{detailObj.content || '无内容描述'}}</text>
:type="isEditing ? 'success' : 'primary'" <textarea
class="action-btn" class="edit-textarea"
:loading="isLoading" v-model="editTitle"
> v-else
{{isEditing ? '保存修改' : '修改'}} placeholder="请输入内容"
</u-button> auto-height
<u-button />
@click="handleWithdraw" </view>
:type="isEditing ? 'default' : 'primary'" </view>
class="action-btn"
> <!-- 操作按钮 -->
{{isEditing ? '取消编辑' : '撤回'}} <view class="btn-group">
</u-button> <u-button
</view> @click="handleModify"
</view> :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> </template>
<script> <script>
import { get, put } from '@/utils/request'; import { get, put } from '@/utils/request';
import { IMAGE_BASE_URL, BASE_URL } from '@/utils/config'; import { IMAGE_BASE_URL, BASE_URL } from '@/utils/config';
import { formatTime } from '@/utils/timeFormat'; import { formatTime } from '@/utils/timeFormat';
export default { export default {
data() { data() {
return { return {
IMAGE_BASE_URL, IMAGE_BASE_URL,
BASE_URL, BASE_URL,
formatTime, formatTime,
isEditing: false, // isEditing: false, //
isLoading: false, // isLoading: false, //
workOrderImg: '', // tempImagePath: '', //
// //
editLabel: '', editLabel: '',
editAddress: '', editTitle: '',
editTitle: '', detailObj: {
detailObj: { images: [], // images
images: [], // images customerPortrait: '',
customerPortrait: '', customerName: '',
customerName: '', createdAt: '',
createdAt: '', title: '',
label: '', content: '',
address: '', id: ''
title: '', },
id: '' btnStyle: {
}, width: '45%',
tempImagePath: '' // height: '80rpx'
}; }
}, };
mounted() { },
let obj = uni.getStorageSync("Detail") || {}; mounted() {
console.log("===boj",obj) let obj = uni.getStorageSync("Detail") || {};
this.detailObj = { this.detailObj = {
...obj //
...obj // };
}; },
// workOrderImg methods: {
if (this.detailObj.images && this.detailObj.images.length > 0) { //
this.workOrderImg = `${IMAGE_BASE_URL}${this.detailObj.images[0]}`; uploadImage() {
} uni.chooseImage({
}, count: 1,
methods: { success: (res) => {
// this.tempImagePath = res.tempFilePaths[0];
uploadImage() { uni.showToast({
uni.chooseImage({ title: '图片已选择',
count: 1, icon: 'success'
success: (res) => { });
const tempFilePaths = res.tempFilePaths[0]; },
this.tempImagePath = tempFilePaths; fail: (err) => {
this.workOrderImg = tempFilePaths; console.error('选择图片失败:', err);
uni.showToast({ uni.showToast({
title: '图片已更换', title: '选择图片失败',
icon: 'success' icon: 'none'
}); });
}, }
fail: (err) => { });
console.error('选择图片失败:', err); },
uni.showToast({
title: '选择图片失败', //
icon: 'none' async uploadImageToServer(filePath) {
}); try {
} const res = await new Promise((resolve, reject) => {
}); uni.uploadFile({
}, url: `${BASE_URL}/api/v1/upload`,
filePath: filePath,
// name: 'file',
async uploadImageToServer(filePath) { header: {
try { 'Authorization': `Bearer ${uni.getStorageSync('token')}`
const res = await new Promise((resolve, reject) => { },
uni.uploadFile({ success: (uploadRes) => {
url: `${BASE_URL}/api/v1/upload`, try {
filePath: filePath, const res = JSON.parse(uploadRes.data);
name: 'file', resolve(res);
header: { } catch (e) {
'Authorization': `Bearer ${uni.getStorageSync('token')}` reject(new Error('解析响应失败'));
}, }
success: (uploadRes) => { },
try { fail: (err) => {
const res = JSON.parse(uploadRes.data); reject(new Error('上传失败: ' + JSON.stringify(err)));
resolve(res); }
} catch (e) { });
reject(new Error('解析响应失败')); });
}
}, if (res && res.success) {
fail: (err) => { return res.data;
reject(new Error('上传失败: ' + JSON.stringify(err))); } else {
} throw new Error(res.message || '上传失败');
}); }
}); } catch (error) {
console.error('上传图片失败:', error);
if (res && res.success) { uni.showToast({
return res.data; // title: '上传图片失败',
} else { icon: 'none'
throw new Error(res.message || '上传失败'); });
} throw error;
} catch (error) { }
console.error('上传图片失败:', error); },
uni.showToast({
title: '上传图片失败', // /
icon: 'none' async handleModify() {
}); if (!this.isEditing) {
throw error; //
} this.editLabel = this.detailObj.title || '';
}, this.editTitle = this.detailObj.content || '';
this.isEditing = true;
// / } else {
async handleModify() { //
if (!this.isEditing) { this.isLoading = true;
//
this.editLabel = this.detailObj.title || ''; try {
this.editAddress = this.detailObj.address || ''; let images = [...(this.detailObj.images || [])];
this.editTitle = this.detailObj.content || '';
if (!this.workOrderImg && this.detailObj.images && this.detailObj.images.length > 0) { //
this.workOrderImg = `${IMAGE_BASE_URL}${this.detailObj.images[0]}`; if (this.tempImagePath) {
} const uploadedPath = await this.uploadImageToServer(this.tempImagePath);
this.isEditing = true; images = [uploadedPath];
} else { }
//
this.isLoading = true; const requestData = {
content: this.editTitle,
try { title: this.editLabel,
let images = [...(this.detailObj.images || [])]; images: images,
status: 1
// };
if (this.tempImagePath) {
const uploadedPath = await this.uploadImageToServer(this.tempImagePath); const res = await put(`/api/v1/app_auth/reciprocities/${this.detailObj.id}`, requestData);
images = [uploadedPath]; //
this.workOrderImg = `${IMAGE_BASE_URL}${uploadedPath}`; if (res && res.success) {
} this.detailObj = {
...this.detailObj,
const requestData = { ...requestData
content: this.editTitle, };
title: this.editLabel,
images: images, uni.showToast({
status: 1 title: '修改成功',
}; icon: 'success'
});
const res = await put(`/api/v1/app_auth/reciprocities/${this.detailObj.id}`, requestData);
this.isEditing = false;
if (res && res.success) { this.tempImagePath = '';
// } else {
this.detailObj = { throw new Error(res.message || '修改失败');
...this.detailObj, }
...requestData } catch (error) {
}; console.error('保存失败:', error);
uni.showToast({
uni.showToast({ title: error.message || '保存失败,请重试',
title: '修改成功', icon: 'none'
icon: 'success' });
}); } finally {
this.isLoading = false;
this.isEditing = false; }
this.tempImagePath = ''; // }
},
// workOrderImg
if (images.length > 0) { // /
this.workOrderImg = `${IMAGE_BASE_URL}${images[0]}`; async handleWithdraw() {
} if (this.isEditing) {
} else { //
throw new Error(res.message || '修改失败'); this.isEditing = false;
} this.tempImagePath = '';
} catch (error) { } else {
console.error('保存失败:', error); //
uni.showToast({ try {
title: error.message || '保存失败,请重试', this.isLoading = true;
icon: 'none' const res = await put(`/api/v1/app_auth/reciprocities/${this.detailObj.id}`, {status: 98});
});
} finally { if (res && res.success) {
this.isLoading = false; uni.showToast({
} title: '撤回成功',
} icon: 'success'
}, });
uni.navigateBack();
// / } else {
async handleWithdraw() { throw new Error(res.message || '撤回失败');
if (this.isEditing) { }
// } catch (error) {
this.isEditing = false; console.error('撤回失败:', error);
this.tempImagePath = ''; uni.showToast({
// title: error.message || '撤回失败',
if (this.detailObj.images && this.detailObj.images.length > 0) { icon: 'none'
this.workOrderImg = `${IMAGE_BASE_URL}${this.detailObj.images[0]}`; });
} } finally {
} else { this.isLoading = false;
// }
try { }
this.isLoading = true; }
const res = await put(`/api/v1/app_auth/reciprocities/${this.detailObj.id}`, {status: 98}); }
};
if (res && res.success) {
uni.showToast({
title: '撤回成功',
icon: 'success'
});
//
uni.navigateBack();
} else {
throw new Error(res.message || '撤回失败');
}
} catch (error) {
console.error('撤回失败:', error);
uni.showToast({
title: error.message || '撤回失败',
icon: 'none'
});
} finally {
this.isLoading = false;
}
}
}
}
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
/* 原有样式保持不变 */ //
// $primary-color: #007AFF;
$primary-color: #007AFF; $success-color: #4cd964;
$success-color: #4cd964; $warning-color: #FF9500;
$warning-color: #FF9500; $error-color: #dd524d;
$text-color: #333; $text-color: #333;
$subtext-color: #999; $subtext-color: #999;
$border-radius: 10rpx; $border-radius: 12rpx;
$padding-base: 15rpx; $padding-base: 20rpx;
$font-base: 32rpx; $font-base: 32rpx;
$shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
// mixin .work-order-detail {
@mixin flex-center { padding: $padding-base;
display: flex; width: 92%;
align-items: center; 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;
.work-order-detail { .avatar {
padding: $padding-base; width: 80rpx;
width: 90%; height: 80rpx;
margin: 0 auto; border-radius: 50%;
border-radius: 10rpx; margin-right: 20rpx;
box-shadow: 0rpx 2rpx 10rpx rgba(0, 0, 0, 0.25); background-color: #f5f5f5;
}
.header { .username {
@include flex-center; font-size: $font-base;
margin-bottom: $padding-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;
}
}
}
.avatar { .title-area {
width: 80rpx; display: flex;
height: 80rpx; align-items: center;
border-radius: 50%; justify-content: space-between;
margin-right: 10rpx; margin-bottom: $padding-base;
} padding-bottom: $padding-base;
border-bottom: 1rpx solid #f5f5f5;
&.editing {
border-bottom: 1rpx solid #eaeaea;
}
.username { .title {
font-size: $font-base; font-size: $font-base + 4rpx;
font-weight: bold; font-weight: bold;
color: $text-color; color: $text-color;
} flex: 1;
} }
.image-area { .title-input {
position: relative; font-size: $font-base + 4rpx;
margin-bottom: $padding-base; font-weight: bold;
min-height: 200rpx; color: $text-color;
background-color: #f5f5f5; flex: 1;
display: flex; padding: 10rpx;
align-items: center; background: #f8f8f8;
justify-content: center; border-radius: 8rpx;
} border: 1rpx solid #eaeaea;
}
.work-order-img { .date {
width: 100%; font-size: $font-base - 4rpx;
border-radius: $border-radius; color: $subtext-color;
max-height: 400rpx; margin-left: 20rpx;
} }
}
.upload-btn {
position: absolute;
bottom: 20rpx;
right: 20rpx;
background: rgba(0, 0, 0, 0.6);
padding: 8rpx 20rpx;
border-radius: 30rpx;
@include flex-center;
.upload-text {
color: #fff;
font-size: 24rpx;
margin-left: 8rpx;
}
}
.title-area { .content {
@include flex-center; margin-bottom: $padding-base * 2;
justify-content: space-between; font-size: $font-base;
margin-bottom: $padding-base;
border-bottom: 2rpx solid $subtext-color; &.editing {
height: 70rpx; padding: 10rpx;
line-height: 70rpx; background: #fafafa;
border-radius: $border-radius;
}
.title { .label {
font-size: $font-base + 2rpx; display: flex;
font-weight: bold; flex-direction: column;
color: $text-color;
max-width: 70%; text {
} font-weight: bold;
margin-bottom: 10rpx;
.title-input { }
font-size: $font-base + 2rpx;
font-weight: bold; .content-text {
color: $text-color; font-weight: normal;
width: 70%; color: #666;
padding: 10rpx; line-height: 1.6;
background: #f8f8f8; }
border-radius: 8rpx; }
}
.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;
}
}
.date { .btn-group {
font-size: $font-base - 4rpx; display: flex;
color: $subtext-color; justify-content: space-between;
} margin-top: 40rpx;
} gap: 20rpx;
.content { .action-btn {
margin-bottom: $padding-base; flex: 1;
line-height: 50rpx; border-radius: 50rpx;
font-size: 24rpx; font-size: $font-base;
font-weight: bold;
.label { }
text { }
font-weight: bold; }
}
margin-bottom: 20rpx;
}
.edit-input {
width: 95%;
padding: 10rpx;
background: #f8f8f8;
border-radius: 8rpx;
margin-top: 5rpx;
}
.edit-textarea {
width: 95%;
padding: 10rpx;
background: #f8f8f8;
border-radius: 8rpx;
margin-top: 5rpx;
min-height: 150rpx;
}
}
.btn-group {
@include flex-center;
justify-content: space-around;
margin-top: 30rpx;
gap:30rpx;
.action-btn {
width: 30%; //
height: 80rpx;
border-radius: 40rpx;
font-size: $font-base - 4rpx;
}
}
}
</style> </style>

View File

@ -3,7 +3,7 @@
<!-- 主帖内容 --> <!-- 主帖内容 -->
<view class="post-container"> <view class="post-container">
<view class="post-header"> <view class="post-header">
<image class="avatar" :src="postData.customerPortrait" mode="aspectFill"></image> <image class="avatar" :src="`${IMAGE_BASE_URL}`+postData.customerPortrait" mode="aspectFill"></image>
<view class="post-info"> <view class="post-info">
<text class="username">{{ postData.customerName }}</text> <text class="username">{{ postData.customerName }}</text>
<text class="time">{{ formatTime(postData.pushAt) }}</text> <text class="time">{{ formatTime(postData.pushAt) }}</text>
@ -32,7 +32,8 @@
<view v-for="comment in flatComments" :key="comment.id" class="comment-item" <view v-for="comment in flatComments" :key="comment.id" class="comment-item"
:class="{'is-reply': comment.toUserName}"> :class="{'is-reply': comment.toUserName}">
<view class="comment-header"> <view class="comment-header">
<image class="avatar" :src="comment.pusherPortrait" mode="aspectFill"></image> <image class="avatar"
:src="comment.pusherPortrait === '/static/imgs/index/nav.png' ? comment.pusherPortrait : `${IMAGE_BASE_URL}${comment.pusherPortrait}`" mode="aspectFill"></image>
<view class="comment-user-info"> <view class="comment-user-info">
<text class="username">{{ comment.pusherName }}</text> <text class="username">{{ comment.pusherName }}</text>
<view class="meta-info"> <view class="meta-info">

View File

@ -41,7 +41,7 @@
</view> </view>
<view class="item_right"> <view class="item_right">
<view class="right_nav"> <view class="right_nav">
<image :src="item.customerPortrait ?item.customerPortrait :`/static/imgs/index/nav.png`" class="nav"></image> <image :src="item.customerPortrait ?`${IMAGE_BASE_URL}`+item.customerPortrait :`/static/imgs/index/nav.png`" class="nav"></image>
<image src="/static/imgs/index/nav_bg.png" class="bg"></image> <image src="/static/imgs/index/nav_bg.png" class="bg"></image>
</view> </view>
<view class="right_name"> <view class="right_name">
@ -317,7 +317,7 @@
.bg { .bg {
position: absolute; position: absolute;
z-index: 1; z-index: 1;
top: 20rpx; top: 0rpx;
left: -118rpx; left: -118rpx;
width: 260rpx; width: 260rpx;
height: 260rpx; height: 260rpx;

View File

@ -37,9 +37,6 @@
工单内容 工单内容
</view> </view>
<textarea v-model="workOrderContent" placeholder="请输入工单详细内容..." class="textarea" /> <textarea v-model="workOrderContent" placeholder="请输入工单详细内容..." class="textarea" />
<view class="voice-icon">
<u-icon name="mic" color="#409EFF" size="36"></u-icon>
</view>
</view> </view>
<!-- 联系人信息 --> <!-- 联系人信息 -->
@ -56,7 +53,13 @@
<u-icon name="phone" color="#3B8CFF" size="38"></u-icon> <u-icon name="phone" color="#3B8CFF" size="38"></u-icon>
电话 电话
</view> </view>
<input v-model="customerPhone" placeholder="请输入联系人电话" class="contact-input" type="number" /> <input
v-model="customerPhone"
placeholder="请输入联系人电话"
class="contact-input"
type="number"
@blur="validatePhone"
/>
</view> </view>
</view> </view>
@ -151,6 +154,19 @@ export default {
this.getOrderTypeList(); this.getOrderTypeList();
}, },
methods: { methods: {
//
validatePhone() {
if (!this.customerPhone) return true;
const phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(this.customerPhone)) {
uni.showToast({
title: '请输入正确的手机号码',
icon: 'none'
});
return false;
}
return true;
},
// //
showUploadAction() { showUploadAction() {
uni.showActionSheet({ uni.showActionSheet({
@ -350,8 +366,11 @@ export default {
this.fileList.splice(index, 1); this.fileList.splice(index, 1);
}, },
// ...
async handleSubmit() { async handleSubmit() {
//
if (!this.validatePhone()) {
return;
}
if (!this.workOrderContent) { if (!this.workOrderContent) {
uni.showToast({ uni.showToast({
title: '请填写工单内容', title: '请填写工单内容',