492 lines
10 KiB
Vue
492 lines
10 KiB
Vue
<template>
|
||
<view class="container">
|
||
<view class="header">
|
||
<text class="back-btn" @click="goBack">‹ 返回</text>
|
||
<text class="title">编辑工单</text>
|
||
<text class="delete-btn" @click="handleDelete">删除</text>
|
||
</view>
|
||
|
||
<scroll-view scroll-y class="content">
|
||
<!-- 加载中 -->
|
||
<view v-if="pageLoading" class="loading">
|
||
<text>加载中...</text>
|
||
</view>
|
||
|
||
<!-- 错误 -->
|
||
<view v-else-if="pageError" class="error">
|
||
<text>{{ pageError }}</text>
|
||
</view>
|
||
|
||
<!-- 表单 -->
|
||
<view v-else class="card">
|
||
<!-- 工单标题 -->
|
||
<view class="form-item">
|
||
<text class="form-label">工单标题 <text class="required">*</text></text>
|
||
<input
|
||
v-model="form.title"
|
||
class="form-input"
|
||
type="text"
|
||
maxlength="200"
|
||
placeholder="请输入工单标题"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 问题描述 -->
|
||
<view class="form-item">
|
||
<text class="form-label">问题描述</text>
|
||
<textarea
|
||
v-model="form.description"
|
||
class="form-textarea"
|
||
placeholder="请输入问题描述"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 图片上传 -->
|
||
<view class="form-item">
|
||
<text class="form-label">图片</text>
|
||
<view class="photo-upload">
|
||
<view
|
||
v-for="(photo, index) in photos"
|
||
:key="photo.ID || index"
|
||
class="photo-item"
|
||
@click="previewPhoto(index)"
|
||
>
|
||
<image
|
||
class="photo-img"
|
||
:src="getPhotoUrl(photo.Sha256)"
|
||
mode="aspectFill"
|
||
/>
|
||
<view class="photo-remove" @click.stop="removePhoto(index)">×</view>
|
||
</view>
|
||
<view v-if="photos.length < 9" class="photo-add" @click="chooseImage">
|
||
<text class="photo-add-icon">+</text>
|
||
<text class="photo-add-text">添加图片</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 提交按钮 -->
|
||
<view class="submit-bar">
|
||
<view class="submit-btn" :class="{ disabled: submitting }" @click="submitForm">
|
||
<text v-if="submitting">保存中...</text>
|
||
<text v-else>保存修改</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 删除确认 -->
|
||
<view v-if="showDeleteConfirm" class="mask" @click="showDeleteConfirm = false">
|
||
<view class="confirm-dialog" @click.stop>
|
||
<text class="confirm-title">确认删除</text>
|
||
<text class="confirm-content">确定要删除这个工单吗?此操作无法撤销。</text>
|
||
<view class="confirm-btns">
|
||
<view class="confirm-cancel" @click="showDeleteConfirm = false">取消</view>
|
||
<view class="confirm-ok" @click="doDelete">删除</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, onMounted } from 'vue'
|
||
import { workOrderApi } from '@/api/work_order.js'
|
||
import api from '@/api/index.js'
|
||
import { useConfigStore } from '@/stores/config.js'
|
||
|
||
const configStore = useConfigStore()
|
||
const goBack = () => uni.navigateBack()
|
||
|
||
// 路由参数
|
||
let orderId = null
|
||
|
||
// 状态
|
||
const pageLoading = ref(true)
|
||
const pageError = ref('')
|
||
const submitting = ref(false)
|
||
const showDeleteConfirm = ref(false)
|
||
|
||
// 表单数据
|
||
const form = reactive({
|
||
title: '',
|
||
description: ''
|
||
})
|
||
const photos = ref([])
|
||
|
||
// 获取图片 URL
|
||
function getPhotoUrl(hash) {
|
||
return configStore.getFileBaseUrl() + '/api/files/get/' + hash
|
||
}
|
||
|
||
// 预览图片
|
||
function previewPhoto(index) {
|
||
const urls = photos.value.map(p => getPhotoUrl(p.Sha256))
|
||
uni.previewImage({
|
||
current: index,
|
||
urls: urls
|
||
})
|
||
}
|
||
|
||
// 加载工单数据
|
||
async function fetchOrder() {
|
||
pageLoading.value = true
|
||
try {
|
||
const res = await workOrderApi.get(orderId)
|
||
if (res.errCode === 0 && res.data) {
|
||
const order = res.data.order || {}
|
||
form.title = order.Title || ''
|
||
form.description = order.Description || ''
|
||
photos.value = res.data.photos || []
|
||
} else {
|
||
pageError.value = '工单不存在'
|
||
}
|
||
} catch (e) {
|
||
console.error('获取工单失败', e)
|
||
pageError.value = '加载失败'
|
||
} finally {
|
||
pageLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 选择图片
|
||
function chooseImage() {
|
||
uni.chooseImage({
|
||
count: 9 - photos.value.length,
|
||
success: (res) => {
|
||
res.tempFilePaths.forEach(path => {
|
||
uploadImage(path)
|
||
})
|
||
}
|
||
})
|
||
}
|
||
|
||
// 上传图片
|
||
async function uploadImage(filePath) {
|
||
try {
|
||
uni.showLoading({ title: '上传中...' })
|
||
const res = await api.upload('/files/upload/image', {
|
||
uri: filePath,
|
||
name: 'file'
|
||
})
|
||
|
||
if (res.errCode === 0 && res.data && res.data.hash) {
|
||
photos.value.push({ Sha256: res.data.hash })
|
||
uni.showToast({ title: '上传成功', icon: 'success' })
|
||
} else {
|
||
uni.showToast({ title: '上传失败', icon: 'none' })
|
||
}
|
||
} catch (e) {
|
||
console.error('上传失败', e)
|
||
uni.showToast({ title: '上传失败', icon: 'none' })
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
}
|
||
|
||
// 移除图片
|
||
function removePhoto(index) {
|
||
photos.value.splice(index, 1)
|
||
}
|
||
|
||
// 提交表单
|
||
async function submitForm() {
|
||
if (!form.title.trim()) {
|
||
uni.showToast({ title: '请输入工单标题', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
if (submitting.value) return
|
||
submitting.value = true
|
||
|
||
try {
|
||
const data = {
|
||
id: orderId,
|
||
title: form.title.trim(),
|
||
description: form.description.trim(),
|
||
photos: photos.value.map(p => p.Sha256)
|
||
}
|
||
|
||
const res = await workOrderApi.update(data)
|
||
|
||
if (res.errCode === 0) {
|
||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||
uni.$emit('page-refresh')
|
||
setTimeout(() => {
|
||
uni.navigateBack()
|
||
}, 500)
|
||
} else {
|
||
uni.showToast({ title: res.errMsg || '保存失败', icon: 'none' })
|
||
}
|
||
} catch (e) {
|
||
console.error('保存失败', e)
|
||
uni.showToast({ title: '保存失败', icon: 'none' })
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
// 删除工单
|
||
function handleDelete() {
|
||
showDeleteConfirm.value = true
|
||
}
|
||
|
||
async function doDelete() {
|
||
try {
|
||
const res = await workOrderApi.delete(orderId)
|
||
if (res.errCode === 0) {
|
||
uni.showToast({ title: '删除成功', icon: 'success' })
|
||
uni.$emit('page-refresh')
|
||
setTimeout(() => {
|
||
uni.navigateBack()
|
||
}, 500)
|
||
} else {
|
||
uni.showToast({ title: '删除失败', icon: 'none' })
|
||
}
|
||
} catch (e) {
|
||
console.error('删除失败', e)
|
||
uni.showToast({ title: '删除失败', icon: 'none' })
|
||
}
|
||
showDeleteConfirm.value = false
|
||
}
|
||
|
||
onMounted(() => {
|
||
const pages = getCurrentPages()
|
||
const currentPage = pages[pages.length - 1]
|
||
const options = currentPage.options || currentPage.$page?.options || {}
|
||
orderId = options.id ? parseInt(options.id) : null
|
||
|
||
if (orderId) {
|
||
fetchOrder()
|
||
} else {
|
||
pageError.value = '工单不存在'
|
||
pageLoading.value = false
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.container {
|
||
min-height: 100vh;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.header {
|
||
background-color: #fff;
|
||
padding: 30rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.back-btn {
|
||
font-size: 32rpx;
|
||
color: #007AFF;
|
||
}
|
||
|
||
.title {
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
flex: 1;
|
||
text-align: center;
|
||
}
|
||
|
||
.delete-btn {
|
||
font-size: 28rpx;
|
||
color: #ff4d4f;
|
||
}
|
||
|
||
.content {
|
||
height: calc(100vh - 120rpx);
|
||
padding: 20rpx;
|
||
}
|
||
|
||
.loading, .error {
|
||
text-align: center;
|
||
padding: 100rpx 0;
|
||
color: #999;
|
||
}
|
||
|
||
.error {
|
||
color: #ff4d4f;
|
||
}
|
||
|
||
.card {
|
||
background-color: #fff;
|
||
border-radius: 12rpx;
|
||
padding: 30rpx;
|
||
}
|
||
|
||
.form-item {
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.form-label {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
margin-bottom: 15rpx;
|
||
}
|
||
|
||
.required {
|
||
color: #ff4d4f;
|
||
}
|
||
|
||
.form-input {
|
||
width: 100%;
|
||
height: 80rpx;
|
||
padding: 0 20rpx;
|
||
background-color: #f5f5f5;
|
||
border-radius: 10rpx;
|
||
font-size: 28rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.form-textarea {
|
||
width: 100%;
|
||
min-height: 200rpx;
|
||
padding: 20rpx;
|
||
background-color: #f5f5f5;
|
||
border-radius: 10rpx;
|
||
font-size: 28rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.photo-upload {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.photo-item {
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
border-radius: 10rpx;
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
|
||
.photo-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.photo-remove {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
color: #fff;
|
||
font-size: 32rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.photo-add {
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
background-color: #f5f5f5;
|
||
border: 2rpx dashed #d9d9d9;
|
||
border-radius: 10rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.photo-add-icon {
|
||
font-size: 60rpx;
|
||
color: #999;
|
||
line-height: 1;
|
||
}
|
||
|
||
.photo-add-text {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-top: 10rpx;
|
||
}
|
||
|
||
.submit-bar {
|
||
margin-top: 40rpx;
|
||
}
|
||
|
||
.submit-btn {
|
||
background-color: #007AFF;
|
||
color: #fff;
|
||
font-size: 32rpx;
|
||
text-align: center;
|
||
padding: 30rpx;
|
||
border-radius: 12rpx;
|
||
}
|
||
|
||
.submit-btn.disabled {
|
||
background-color: #ccc;
|
||
}
|
||
|
||
/* 删除确认弹窗 */
|
||
.mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.confirm-dialog {
|
||
width: 560rpx;
|
||
background-color: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 40rpx;
|
||
}
|
||
|
||
.confirm-title {
|
||
display: block;
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
text-align: center;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.confirm-content {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
text-align: center;
|
||
margin-bottom: 40rpx;
|
||
}
|
||
|
||
.confirm-btns {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.confirm-cancel {
|
||
flex: 1;
|
||
text-align: center;
|
||
padding: 24rpx;
|
||
background-color: #f5f5f5;
|
||
border-radius: 10rpx;
|
||
font-size: 32rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.confirm-ok {
|
||
flex: 1;
|
||
text-align: center;
|
||
padding: 24rpx;
|
||
background-color: #ff4d4f;
|
||
border-radius: 10rpx;
|
||
font-size: 32rpx;
|
||
color: #fff;
|
||
}
|
||
</style>
|