相关修改

This commit is contained in:
qiuyuan 2025-08-06 17:55:50 +08:00
parent 6dea318ca8
commit 4022a1ebcd
4 changed files with 718 additions and 536 deletions

View File

@ -96,7 +96,7 @@
<text class="card-desc">维修方便便捷</text> <text class="card-desc">维修方便便捷</text>
<view class="card-bg"></view> <view class="card-bg"></view>
</view> </view>
<image class="card-icon" src="/static/imgs/index/weixiu.png"></image> <image class="card-icon" src="/static/imgs/index/weixiu.png" style="width: 80rpx;height: 80rpx;bottom: 32rpx;"></image>
</view> </view>
<!-- <view class="service-card large card-3" @click="goDetail('neighborList')" hover-class="card-hover"> <!-- <view class="service-card large card-3" @click="goDetail('neighborList')" hover-class="card-hover">
@ -522,13 +522,13 @@
&.card-2 { &.card-2 {
.card-bg { .card-bg {
background: #ff7e7e; background: #5b9cf8;
top: -30rpx; top: -50rpx;
right: -30rpx; right: -30rpx;
} }
.card-icon { .card-icon {
filter: drop-shadow(0 4rpx 8rpx rgba(255, 126, 126, 0.3)); filter: drop-shadow(0 4rpx 8rpx rgba(91, 156, 248, 0.3));
} }
} }

View File

@ -42,7 +42,7 @@
<view class="date">{{ formatTime(order.createdAt,"YYYY-MM-DD") }}</view> <view class="date">{{ formatTime(order.createdAt,"YYYY-MM-DD") }}</view>
</view> </view>
<view class="order-description">{{ order.title }}</view> <view class="order-description">{{ order.title }}</view>
<button @click="handleAction(order)" class="order-action" v-if="order.status == 1 || order.status == 2 || order.status == 99">撤回</button> <button @click.stop="handleAction(order, $event)" class="order-action" v-if="order.status == 1 || order.status == 2 || order.status == 99">撤回</button>
</view> </view>
</view> </view>
<!-- 空状态提示 --> <!-- 空状态提示 -->
@ -73,7 +73,7 @@
<script> <script>
// import Header from '@/components/header_common.vue'; // import Header from '@/components/header_common.vue';
import Footer from '@/components/footer_common.vue'; import Footer from '@/components/footer_common.vue';
import { get, post } from '@/utils/request'; import { get, post,put } from '@/utils/request';
import { IMAGE_BASE_URL,BASE_URL } from '@/utils/config'; import { IMAGE_BASE_URL,BASE_URL } from '@/utils/config';
import { formatTime, formatRelativeTime } from '@/utils/timeFormat'; import { formatTime, formatRelativeTime } from '@/utils/timeFormat';
export default { export default {
@ -116,7 +116,7 @@
2: '进行中', 2: '进行中',
3: '已完成', 3: '已完成',
99:'驳回', 99:'驳回',
98:'已删除' 98:'已撤回'
}; };
return statusMap[status] || '未知状态'; return statusMap[status] || '未知状态';
}, },
@ -153,20 +153,39 @@
uni.navigateTo({ url: '/pages/serviceTickets/index' }); uni.navigateTo({ url: '/pages/serviceTickets/index' });
}, },
// //
handleAction(order) { handleAction(order, e) {
if (order.actionText === '撤回') { //
e.stopPropagation();
if (order.status == 1 || order.status == 2 || order.status == 99) {
uni.showModal({ uni.showModal({
title: '提示', title: '提示',
content: '确定要撤回这个工单吗?', content: '确定要撤回这个工单吗?',
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
// //
this.withdrawOrder(order.id);
}
}
});
}
},
async withdrawOrder(orderId) {
try {
const res = await put(`/api/v1/app_auth/work-order/${orderId}`, {status:98});
if (res?.success) {
uni.showToast({ uni.showToast({
title: '工单已撤回', title: '工单已撤回',
icon: 'success' icon: 'success'
}); });
//
this.getWorkOrderList();
} }
} } catch (err) {
console.error('撤回工单失败:', err);
uni.showToast({
title: '撤回失败',
icon: 'none'
}); });
} }
}, },

View File

@ -4,15 +4,34 @@
<!-- 图片区域 - 编辑状态下可修改 --> <!-- 图片区域 - 编辑状态下可修改 -->
<view class="image-area card"> <view class="image-area card">
<view class="img-container"> <view class="img-container">
<!-- 非编辑状态显示服务器图片 -->
<image <image
class="work-order-img" class="work-order-img"
:src="!isEditing ? `${IMAGE_BASE_URL}${detailObj.images[0] || ''}` : workOrderImg" :src="`${IMAGE_BASE_URL}${detailObj.images[0]}`"
mode="widthFix" mode="widthFix"
:lazy-load="true" lazy-load="true"
v-if="!isEditing && detailObj.images && detailObj.images.length > 0"
></image> ></image>
<!-- 编辑状态显示本地选择的图片 -->
<image
class="work-order-img"
:src="workOrderImg"
mode="widthFix"
lazy-load="true"
v-if="isEditing && workOrderImg"
></image>
<!-- 没有图片时的占位 -->
<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" @click="uploadImage" v-if="isEditing"> <view class="upload-btn" @click="uploadImage" v-if="isEditing">
<u-icon name="camera" size="28" color="#fff"></u-icon> <u-icon name="camera" size="28" color="#fff"></u-icon>
<text class="upload-text">更换图片</text> <text class="upload-text">{{ workOrderImg ? '更换图片' : '上传图片' }}</text>
</view> </view>
</view> </view>
</view> </view>
@ -298,7 +317,22 @@
margin-bottom: 0; margin-bottom: 0;
} }
} }
.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;
}
}
/* 页面容器 */ /* 页面容器 */
.work-order-detail { .work-order-detail {
background-color: $bg-color; background-color: $bg-color;

View File

@ -11,11 +11,7 @@
<u-icon name="arrow-down" color="#999" size="30"></u-icon> <u-icon name="arrow-down" color="#999" size="30"></u-icon>
</view> </view>
</view> </view>
<u-picker <u-picker :show="showTypeSelect" :columns="areaList" keyName="title" @confirm="handleTypeConfirm"
:show="showTypeSelect"
:columns="areaList"
keyName="title"
@confirm="handleTypeConfirm"
@cancel="showTypeSelect = false"> @cancel="showTypeSelect = false">
</u-picker> </u-picker>
</view> </view>
@ -35,7 +31,6 @@
</u-picker> </u-picker>
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="label"> <view class="label">
<u-icon name="order" color="#3B8CFF" size="38"></u-icon> <u-icon name="order" color="#3B8CFF" size="38"></u-icon>
@ -65,21 +60,29 @@
</view> </view>
</view> </view>
<!-- 上传照片/视频 --> <!-- 上传照片/视频 - 完全重写的上传组件 -->
<view class="form-item"> <view class="form-item">
<view class="label"> <view class="label">
<u-icon name="photo" color="#3B8CFF" size="38"></u-icon> <u-icon name="photo" color="#3B8CFF" size="38"></u-icon>
上传照片/视频 上传照片/视频
</view> </view>
<view class="upload-area"> <view class="upload-area">
<u-upload :fileList="fileList" @afterRead="handleAfterRead" @delete="handleDelete" :maxCount="8" <view class="upload-list">
width="160" height="160" accept="image/*,video/*" :previewFullImage="true"> <view class="upload-item" v-for="(item, index) in fileList" :key="index">
</u-upload> <image v-if="!item.isVideo" :src="item.url" mode="aspectFill" @click="previewImage(index)"></image>
<video v-else :src="item.url" controls></video>
<view class="delete-btn" @click="handleDelete(index)">
<u-icon name="close" color="#fff" size="24"></u-icon>
</view>
</view>
<view class="upload-btn" @click="showUploadAction" v-if="fileList.length < 8">
<u-icon name="plus" size="40" color="#c0c4cc"></u-icon>
</view>
</view>
</view> </view>
<text class="note">照片可识别工单内容支持分批上传但最大数量为8</text> <text class="note">照片可识别工单内容支持分批上传但最大数量为8</text>
</view> </view>
<!-- 工单地点 --> <!-- 工单地点 -->
<view class="form-item"> <view class="form-item">
<view class="label"> <view class="label">
@ -95,28 +98,31 @@
</view> </view>
</view> </view>
<Footer></Footer> <Footer></Footer>
<!-- 上传操作菜单 -->
<u-action-sheet
:list="actionList"
v-model="showActionSheet"
@click="handleActionClick"
></u-action-sheet>
</view> </view>
</template> </template>
<script> <script>
import Footer from '@/components/footer_common.vue'; import Footer from '@/components/footer_common.vue';
import { import { get, post } from '@/utils/request';
get, import { IMAGE_BASE_URL, BASE_URL } from '@/utils/config';
post import uActionSheet from 'uview-ui/components/u-action-sheet/u-action-sheet';
} from '@/utils/request';
import {
IMAGE_BASE_URL,
BASE_URL
} from '@/utils/config';
export default { export default {
components: { components: {
Footer Footer,uActionSheet
}, },
data() { data() {
return { return {
workOrderContent: '', workOrderContent: '',
concatName: '', // concatName: '',
customerPhone: '', // customerPhone: '',
fileList: [], fileList: [],
workOrderCategory: '', workOrderCategory: '',
workOrderCategoryID: '', workOrderCategoryID: '',
@ -131,6 +137,13 @@
uploadedVideos: [], uploadedVideos: [],
videoFormats: ['mp4', 'mov', 'avi', 'wmv', 'mpeg', '3gp', 'flv', 'mkv'], videoFormats: ['mp4', 'mov', 'avi', 'wmv', 'mpeg', '3gp', 'flv', 'mkv'],
areaList: [], areaList: [],
//
showActionSheet: false,
actionList: [
{ text: '拍照', value: 'camera' },
{ text: '从相册选择', value: 'album' }
]
}; };
}, },
mounted() { mounted() {
@ -138,129 +151,206 @@
this.getOrderTypeList(); this.getOrderTypeList();
}, },
methods: { methods: {
handleBack() { //
uni.navigateBack({ showUploadAction() {
delta: 1 uni.showActionSheet({
itemList: ['拍照', '从相册选择'],
success: (res) => {
if (res.tapIndex === 0) {
this.chooseMedia('camera');
} else {
this.chooseMedia('album');
}
}
}); });
}, },
// //
handleAfterRead(event) { handleActionClick(index) {
const { const action = this.actionList[index].value;
file if (action === 'camera') {
} = event; this.chooseMedia('camera');
let files = [].concat(file); } else {
this.chooseMedia('album');
}
},
if (this.fileList.length + files.length > 8) { //
uni.showToast({ async chooseMedia(sourceType) {
title: '最多只能上传8个文件', try {
icon: 'none' // 1.
const res = await new Promise((resolve, reject) => {
uni.chooseImage({
count: 8 - this.fileList.length,
sourceType: [sourceType === 'camera' ? 'camera' : 'album'],
success: resolve,
fail: reject
}); });
return; });
// 2.
const newFiles = res.tempFilePaths.map(url => ({
url,
isVideo: false,
status: 'uploading'
}));
this.fileList = [...this.fileList, ...newFiles];
// 3.
for (const file of newFiles) {
await this.uploadFile(file);
} }
files.forEach(item => { } catch (err) {
const extension = this.getFileExtension(item.url); console.error('选择图片失败:', err);
const isVideo = this.videoFormats.includes(extension.toLowerCase()); uni.showToast({ title: '选择图片失败', icon: 'none' });
}
},
_chooseMedia(sourceType) {
uni.chooseMedia({
count: 8 - this.fileList.length,
mediaType: ['image', 'video'],
sourceType: [sourceType],
success: async (res) => {
//
const newFiles = res.tempFiles.map(file => ({
url: file.tempFilePath,
isVideo: file.fileType === 'video',
status: 'uploading' //
}));
this.fileList = [...this.fileList, ...newFiles];
this.fileList.push({ //
...item, for (const file of newFiles) {
status: 'uploading', await this.uploadFile(file); //
message: '上传中...', }
isVideo: isVideo }
});
this.uploadFile(item, isVideo);
}); });
}, },
getFileExtension(filename) { //
return filename.split('.').pop().split('?')[0]; uploadFiles(files) {
}, files.forEach(file => {
this.uploadFile(file).then(res => {
handleDelete(event) { const index = this.fileList.findIndex(item => item.url === file.url);
const { if (index !== -1) {
index this.$set(this.fileList, index, {
} = event; ...file,
const file = this.fileList[index]; status: 'success',
serverUrl: res
});
if (file.isVideo) { if (file.isVideo) {
const videoIndex = this.uploadedVideos.indexOf(file.url); this.uploadedVideos.push(res.replace(BASE_URL, ''));
} else {
this.uploadedImages.push(res.replace(BASE_URL, ''));
}
}
}).catch(err => {
const index = this.fileList.findIndex(item => item.url === file.url);
if (index !== -1) {
this.$set(this.fileList, index, {
...file,
status: 'failed'
});
}
console.error('上传失败:', err);
});
});
},
//
async uploadFile(file) {
try {
const res = await new Promise((resolve, reject) => {
uni.uploadFile({
url: `${BASE_URL}/api/v1/upload`,
filePath: file.url,
name: 'file',
header: {
'Authorization': `Bearer ${uni.getStorageSync('token')}`,
'Content-Type': 'multipart/form-data'
},
success: (uploadRes) => {
try {
const data = JSON.parse(uploadRes.data);
if (data.success) {
resolve(data.data); // 使URL
} else {
reject(data.message || '上传失败');
}
} catch (e) {
reject('解析响应失败');
}
},
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
});
//
if (file.isVideo) {
this.uploadedVideos.push(res);
} else {
this.uploadedImages.push(res);
}
}
} catch (err) {
console.error('上传失败:', err);
const index = this.fileList.findIndex(f => f.url === file.url);
if (index !== -1) {
this.$set(this.fileList, index, {
...file,
status: 'failed',
error: err.message || err
});
}
uni.showToast({
title: `上传失败: ${err.message || err}`,
icon: 'none'
});
}
},
//
previewImage(index) {
const images = this.fileList
.filter(item => !item.isVideo)
.map(item => item.url);
uni.previewImage({
current: index,
urls: images
});
},
//
handleDelete(index) {
const file = this.fileList[index];
if (file.serverUrl) {
if (file.isVideo) {
const videoIndex = this.uploadedVideos.indexOf(file.serverUrl.replace(BASE_URL, ''));
if (videoIndex !== -1) this.uploadedVideos.splice(videoIndex, 1); if (videoIndex !== -1) this.uploadedVideos.splice(videoIndex, 1);
} else { } else {
const imageIndex = this.uploadedImages.indexOf(file.url); const imageIndex = this.uploadedImages.indexOf(file.serverUrl.replace(BASE_URL, ''));
if (imageIndex !== -1) this.uploadedImages.splice(imageIndex, 1); if (imageIndex !== -1) this.uploadedImages.splice(imageIndex, 1);
} }
}
this.fileList.splice(index, 1); this.fileList.splice(index, 1);
}, },
async uploadFile(file, isVideo) { // ...
try {
const fullUrl = await this.uploadAvatar(file.url);
const relativePath = fullUrl.replace(BASE_URL, '');
const fileIndex = this.fileList.findIndex(item => item.url === file.url);
if (fileIndex !== -1) {
this.fileList[fileIndex] = {
...this.fileList[fileIndex],
status: 'success',
message: '上传成功',
url: fullUrl,
relativeUrl: relativePath,
isVideo: isVideo
};
if (isVideo) {
this.uploadedVideos.push(relativePath);
} else {
this.uploadedImages.push(relativePath);
}
}
} catch (error) {
const fileIndex = this.fileList.findIndex(item => item.url === file.url);
if (fileIndex !== -1) {
this.fileList[fileIndex] = {
...this.fileList[fileIndex],
status: 'failed',
message: '上传失败'
};
}
uni.showToast({
title: `${isVideo ? '视频' : '图片'}上传失败: ${error.message}`,
icon: 'none'
});
}
},
uploadAvatar(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(`${IMAGE_BASE_URL}${res.data}`);
} else {
reject(new Error(res.message || '上传失败'));
}
} catch (e) {
reject(new Error('解析响应失败'));
}
},
fail: (err) => {
reject(new Error('上传失败: ' + JSON.stringify(err)));
}
});
});
},
async handleSubmit() { async handleSubmit() {
if (!this.workOrderContent) { if (!this.workOrderContent) {
uni.showToast({ uni.showToast({
@ -304,8 +394,8 @@
const submitData = { const submitData = {
title: this.workOrderContent, title: this.workOrderContent,
concatName: this.concatName, // concatName: this.concatName,
customerPhone: this.customerPhone, // customerPhone: this.customerPhone,
orderTypeId: this.workOrderCategoryID, orderTypeId: this.workOrderCategoryID,
orderAreaId: this.workOrderAreaID, orderAreaId: this.workOrderAreaID,
address: this.workOrderLocation, address: this.workOrderLocation,
@ -315,7 +405,6 @@
console.log('提交数据:', submitData); console.log('提交数据:', submitData);
// API
const res = await post('/api/v1/app_auth/work-order', submitData); const res = await post('/api/v1/app_auth/work-order', submitData);
uni.hideLoading(); uni.hideLoading();
@ -387,7 +476,6 @@
} }
}, },
//
async getOrderTypeList() { async getOrderTypeList() {
try { try {
const res = await get('/api/v1/apps/work-order-area'); const res = await get('/api/v1/apps/work-order-area');
@ -467,6 +555,47 @@
.upload-area { .upload-area {
margin-top: 16rpx; 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, video {
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 { .note {