qiuyuan 5d3f1b9c81 1
2025-08-13 10:53:10 +08:00

670 lines
17 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="container">
<view class="content">
<!-- 选择区域 -->
<view class="form-item">
<view class="label" @click="showTypeSelect = true" style="margin-bottom: 0;">
<u-icon name="list" color="#3B8CFF" size="38"></u-icon>
选择区域
<view class="select-box">
{{ workOrderArea || '请选择' }}
<u-icon name="arrow-down" color="#999" size="30"></u-icon>
</view>
</view>
<u-picker :show="showTypeSelect" :columns="areaList" keyName="title" @confirm="handleTypeConfirm"
@cancel="showTypeSelect = false">
</u-picker>
</view>
<!-- 工单分类 -->
<view class="form-item">
<view class="label" @click="showSelect = true" style="margin-bottom: 0;">
<u-icon name="list" color="#3B8CFF" size="38"></u-icon>
工单类型
<view class="select-box">
{{ workOrderCategory || '请选择' }}
<u-icon name="arrow-down" color="#999" size="30"></u-icon>
</view>
</view>
<u-picker :show="showSelect" :columns="categoryList" keyName="label" @confirm="handleConfirm"
@cancel="handleCancel">
</u-picker>
</view>
<view class="form-item">
<view class="label">
<u-icon name="order" color="#3B8CFF" size="38"></u-icon>
工单内容
</view>
<textarea v-model="workOrderContent" placeholder="请输入工单详细内容..." class="textarea" />
</view>
<!-- 联系人信息 -->
<view class="form-item contact-info">
<view class="contact-row">
<view class="label">
<u-icon name="account" color="#3B8CFF" size="38"></u-icon>
联系人
</view>
<input v-model="concatName" placeholder="请输入联系人姓名" class="contact-input" />
</view>
<view class="contact-row">
<view class="label">
<u-icon name="phone" color="#3B8CFF" size="38"></u-icon>
电话
</view>
<input
v-model="customerPhone"
placeholder="请输入联系人电话"
class="contact-input"
type="number"
@blur="validatePhone"
/>
</view>
</view>
<!-- 上传照片/视频 - 完全重写的上传组件 -->
<view class="form-item">
<view class="label">
<u-icon name="photo" color="#3B8CFF" size="38"></u-icon>
上传照片/视频
</view>
<view class="upload-area">
<view class="upload-list">
<view class="upload-item" v-for="(item, index) in fileList" :key="index">
<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>
<text class="note">照片支持分批上传但最大数量为8</text>
</view>
<!-- 工单地点 -->
<view class="form-item">
<view class="label">
<u-icon name="map" color="#3B8CFF" size="38"></u-icon>
维修地点
</view>
<textarea v-model="workOrderLocation" placeholder="请输入详细工作地点..." class="textarea" />
</view>
<!-- 提交按钮 -->
<view class="submit-button">
<u-button type="primary" @click="handleSubmit" shape="circle">提交工单</u-button>
</view>
</view>
<!-- <Footer></Footer> -->
<!-- 上传操作菜单 -->
<u-action-sheet
:list="actionList"
v-model="showActionSheet"
@click="handleActionClick"
></u-action-sheet>
</view>
</template>
<script>
import Footer from '@/components/footer_common.vue';
import { get, post } from '@/utils/request';
import { IMAGE_BASE_URL, BASE_URL } from '@/utils/config';
import uActionSheet from 'uview-ui/components/u-action-sheet/u-action-sheet';
export default {
components: {
Footer,uActionSheet
},
data() {
return {
workOrderContent: '',
concatName: '',
customerPhone: '',
fileList: [],
workOrderCategory: '',
workOrderCategoryID: '',
workOrderArea: '',
workOrderAreaID: '',
showSelect: false,
showTypeSelect: false,
categoryList: [],
workOrderLocation: '',
expectedTime: '',
uploadedImages: [],
uploadedVideos: [],
videoFormats: ['mp4', 'mov', 'avi', 'wmv', 'mpeg', '3gp', 'flv', 'mkv'],
areaList: [],
// 上传相关
showActionSheet: false,
actionList: [
{ text: '拍照', value: 'camera' },
{ text: '从相册选择', value: 'album' }
]
};
},
mounted() {
this.getOrderList();
this.getOrderTypeList();
},
methods: {
// 新增手机号验证方法
validatePhone() {
const phoneReg = /^1[3-9]\d{9}$/;
if (!this.customerPhone) {
uni.showToast({
title: '请填写联系电话',
icon: 'none'
});
return false;
}
if (!phoneReg.test(this.customerPhone)) {
uni.showToast({
title: '请输入正确的手机号码',
icon: 'none'
});
return false;
}
return true;
},
// 显示上传选项
showUploadAction() {
uni.showActionSheet({
itemList: ['拍照', '从相册选择'],
success: (res) => {
if (res.tapIndex === 0) {
this.chooseMedia('camera');
} else {
this.chooseMedia('album');
}
}
});
},
// 处理上传选项选择
handleActionClick(index) {
const action = this.actionList[index].value;
if (action === 'camera') {
this.chooseMedia('camera');
} else {
this.chooseMedia('album');
}
},
// 选择媒体文件
async chooseMedia(sourceType) {
try {
// 1. 选择图片
const res = await new Promise((resolve, reject) => {
uni.chooseImage({
count: 8 - this.fileList.length,
sourceType: [sourceType === 'camera' ? 'camera' : 'album'],
success: resolve,
fail: reject
});
});
// 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);
}
} catch (err) {
console.error('选择图片失败:', err);
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];
// 逐个上传文件
for (const file of newFiles) {
await this.uploadFile(file); // 等待上传完成
}
}
});
},
// 上传文件
uploadFiles(files) {
files.forEach(file => {
this.uploadFile(file).then(res => {
const index = this.fileList.findIndex(item => item.url === file.url);
if (index !== -1) {
this.$set(this.fileList, index, {
...file,
status: 'success',
serverUrl: res
});
if (file.isVideo) {
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: `${IMAGE_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);
} else {
const imageIndex = this.uploadedImages.indexOf(file.serverUrl.replace(BASE_URL, ''));
if (imageIndex !== -1) this.uploadedImages.splice(imageIndex, 1);
}
}
this.fileList.splice(index, 1);
},
async handleSubmit() {
// 在提交前验证手机号
if (!this.validatePhone()) {
return;
}
if (!this.workOrderContent) {
uni.showToast({
title: '请填写工单内容',
icon: 'none'
});
return;
}
if (!this.concatName) {
uni.showToast({
title: '请填写联系人姓名',
icon: 'none'
});
return;
}
if (!this.customerPhone) {
uni.showToast({
title: '请填写联系电话',
icon: 'none'
});
return;
}
if (!this.workOrderCategory) {
uni.showToast({
title: '请选择工单类型',
icon: 'none'
});
return;
}
if (!this.workOrderLocation) {
uni.showToast({
title: '请填写工作地点',
icon: 'none'
});
return;
}
const submitData = {
title: this.workOrderContent,
concatName: this.concatName,
customerPhone: this.customerPhone,
orderTypeId: this.workOrderCategoryID,
orderAreaId: this.workOrderAreaID,
address: this.workOrderLocation,
images: this.uploadedImages,
videos: this.uploadedVideos,
};
console.log('提交数据:', submitData);
const res = await post('/api/v1/app_auth/work-order', submitData);
uni.hideLoading();
if (res && res.success) {
uni.showToast({
title: '提交成功',
icon: 'success',
duration: 2000
});
this.resetForm();
setTimeout(() => {
uni.navigateTo({
url: `/pages/myTickets/index`,
});
}, 1000)
} else {
uni.showToast({
title: res?.message || '提交失败',
icon: 'none'
});
}
},
resetForm() {
this.workOrderContent = '';
this.concatName = '';
this.customerPhone = '';
this.workOrderCategoryID = '';
this.workOrderCategory = '';
this.workOrderLocation = '';
this.fileList = [];
this.uploadedImages = [];
this.uploadedVideos = [];
this.workOrderArea = '';
this.workOrderAreaID = '';
},
handleConfirm(e) {
this.workOrderCategory = e.value[0].label;
this.workOrderCategoryID = e.value[0].id;
this.showSelect = false;
},
handleTypeConfirm(e) {
this.workOrderArea = e.value[0].title;
this.workOrderAreaID = e.value[0].id;
this.showTypeSelect = false;
},
handleCancel() {
this.showSelect = false;
},
async getOrderList() {
try {
const res = await get('/api/v1/apps/work-order-type');
if (res?.success) {
this.categoryList = [
[...res.data]
]
}
} catch (err) {
console.error('获取工单类型失败:', err);
uni.showToast({
title: '获取工单类型失败',
icon: 'none'
});
}
},
async getOrderTypeList() {
try {
const res = await get('/api/v1/apps/work-order-area');
if (res?.success) {
this.areaList = [
[...res.data]
]
}
} catch (err) {
console.error('获取工作区域失败:', err);
uni.showToast({
title: '获取工作区域失败',
icon: 'none'
});
}
}
}
};
</script>
<style lang="scss" scoped>
.container {
width: 100%;
min-height: 100vh;
background: #f5f7fa;
padding-bottom: 120rpx;
.content {
padding: 20rpx;
.form-item {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
.label {
font-size: 32rpx;
color: #333;
font-weight: 500;
margin-bottom: 24rpx;
display: flex;
align-items: center;
.u-icon {
margin-right: 12rpx;
}
}
.textarea {
width: 95%;
height: 180rpx;
padding: 16rpx;
background: #f8f8f8;
border-radius: 8rpx;
font-size: 28rpx;
}
.voice-icon {
position: absolute;
right: 40rpx;
bottom: 40rpx;
}
.select-box {
margin-left: auto;
color: #666;
display: flex;
align-items: center;
font-size: 28rpx;
.u-icon {
margin-left: 8rpx;
}
}
.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, 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 {
font-size: 24rpx;
color: #999;
margin-top: 24rpx;
display: block;
}
}
.contact-info {
.contact-row {
display: flex;
align-items: center;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.label {
margin-bottom: 0;
width: 160rpx;
flex-shrink: 0;
}
.contact-input {
flex: 1;
padding: 16rpx;
background: #f8f8f8;
border-radius: 8rpx;
font-size: 28rpx;
}
}
}
.submit-button {
margin-top: 40rpx;
.u-button {
height: 88rpx;
font-size: 32rpx;
}
}
}
}
</style>