展品维保小程序前端代码接口
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

768 lines
19 KiB

<template>
<view class="maintenance-submit">
<!-- 保养基本信息 -->
<view class="maintenance-info">
<view class="info-header">
<view class="red-line"></view>
<text class="info-title">保养项目</text>
</view>
<!-- 保养人 -->
<view class="form-item">
<text class="label">保养人</text>
<view class="input-area" @click="focusMaintainer">
<input
v-model="maintainer"
placeholder="请填写"
class="input-field"
ref="maintainerInput"
/>
</view>
</view>
<!-- 保养日期 -->
<view class="form-item" @click="showDatePicker">
<text class="label">保养日期</text>
<view class="select-area">
<text class="value" :class="{ placeholder: !maintenanceDate }">{{ maintenanceDate || '请选择' }}</text>
<uv-icon name="arrow-down" size="18" color="#000"></uv-icon>
</view>
</view>
<!-- 保养前状态 -->
<view class="form-item">
<text class="label">保养前状态</text>
</view>
<view class="textarea-container">
<uv-textarea
v-model="beforeStatus"
placeholder="请填写保养前的设备内容"
:maxlength="200"
:show-confirm-bar="false"
height="60"
border="none"
:custom-style="{ backgroundColor: '#f5f5f5' }"
></uv-textarea>
</view>
<!-- 保养前图片 -->
<view class="image-upload">
<view v-for="(img, index) in beforeImageList" :key="index" class="image-item">
<image :src="img" mode="aspectFill" @click="previewImage(img, beforeImageList)"></image>
<view class="delete-btn" @click="deleteBeforeImage(index)">
<uv-icon name="close" size="12" color="#fff"></uv-icon>
</view>
</view>
<view class="upload-btn" @click="uploadBeforeImage">
<uv-icon name="camera" size="34" color="#C70019"></uv-icon>
</view>
</view>
<!-- 保养后状态 -->
<view class="form-item">
<text class="label">保养后状态</text>
</view>
<view class="textarea-container">
<uv-textarea
v-model="afterStatus"
placeholder="请填写保养后的设备内容"
:maxlength="200"
:show-confirm-bar="false"
height="60"
border="none"
:custom-style="{ backgroundColor: '#f5f5f5' }"
></uv-textarea>
</view>
<!-- 保养后图片 -->
<view class="image-upload">
<view v-for="(img, index) in afterImageList" :key="index" class="image-item">
<image :src="img" mode="aspectFill" @click="previewImage(img, afterImageList)"></image>
<view class="delete-btn" @click="deleteAfterImage(index)">
<uv-icon name="close" size="12" color="#fff"></uv-icon>
</view>
</view>
<view class="upload-btn" @click="uploadAfterImage">
<uv-icon name="camera" size="34" color="#C70019"></uv-icon>
</view>
</view>
<!-- 是否产生费用 -->
<view class="form-item">
<text class="label">是否产生费用</text>
<view class="radio-options">
<view
class="radio-item"
:class="{ active: hasCost === true }"
@click="selectCost(true)"
>
<view class="radio-dot" :class="{ active: hasCost === true }"></view>
<text :class="{ active: hasCost === true }">是</text>
</view>
<view
class="radio-item"
:class="{ active: hasCost === false }"
@click="selectCost(false)"
>
<view class="radio-dot" :class="{ active: hasCost === false }"></view>
<text :class="{ active: hasCost === false }">否</text>
</view>
</view>
</view>
<!-- 产生费用 -->
<view v-if="hasCost" class="cost-section">
<view class="form-item form-item-header">
<text class="label active">产生费用</text>
</view>
<view class="cost-input">
<text class="label">费用名称</text>
<uv-input
v-model="newCostName"
placeholder="请输入费用"
border="none"
:custom-style="{ backgroundColor: '#fff', borderRadius: '4px', textAlign: 'right', marginLeft: '24rpx' }"
></uv-input>
</view>
<view class="cost-list">
<view class="cost-header">
<text class="header-item">费用名称</text>
<text class="header-item">数量</text>
<text class="header-item">金额</text>
</view>
<view v-for="(item, index) in costList" :key="index" class="cost-item">
<text class="cost-name">{{ item.name }}</text>
<view class="quantity-control">
<uv-icon
name="minus-circle"
size="20"
:color="item.quantity > 1 ? '#C70019' : '#ccc'"
@click="decreaseQuantity(index)"
></uv-icon>
<text class="quantity">{{ item.quantity }}</text>
<uv-icon
name="plus-circle"
size="20"
color="#C70019"
@click="increaseQuantity(index)"
></uv-icon>
</view>
<uv-input
v-model="item.amount"
placeholder="0.00"
type="digit"
border="none"
:custom-style="{ backgroundColor: '#f5f5f5', borderRadius: '4px', textAlign: 'right' }"
></uv-input>
<uv-icon
name="close-circle"
size="20"
color="#C70019"
@click="removeCostItem(index)"
></uv-icon>
</view>
<view class="add-cost-btn" @click="addCostItem">
<uv-icon name="plus-circle" size="20" color="#C70019"></uv-icon>
<text>添加费用项目</text>
</view>
</view>
</view>
<!-- 附件信息 -->
<view class="form-item form-item-header">
<text class="label active">附件信息</text>
</view>
<!-- 保养备注 -->
<view class="form-item">
<text class="label">保养备注</text>
</view>
<view class="textarea-container">
<uv-textarea
v-model="remark"
placeholder="请填写备注"
:maxlength="200"
:show-confirm-bar="false"
height="60"
border="none"
:custom-style="{ backgroundColor: '#f5f5f5' }"
></uv-textarea>
</view>
<!-- 附件图片 -->
<view class="image-upload">
<view v-for="(img, index) in attachmentList" :key="index" class="image-item">
<image :src="img" mode="aspectFill" @click="previewImage(img, attachmentList)"></image>
<view class="delete-btn" @click="deleteAttachment(index)">
<uv-icon name="close" size="12" color="#fff"></uv-icon>
</view>
</view>
<view class="upload-btn" @click="uploadAttachment">
<uv-icon name="camera" size="34" color="#C70019"></uv-icon>
</view>
</view>
<!-- 下次保养日期 -->
<view class="form-item" @click="showNextDatePicker">
<text class="label">下次保养日期</text>
<view class="select-area">
<text class="value" :class="{ placeholder: !nextMaintenanceDate }">{{ nextMaintenanceDate || '请选择' }}</text>
<uv-icon name="arrow-down" size="18" color="#000"></uv-icon>
</view>
</view>
<!-- 备注 -->
<view class="form-item">
<text class="label">备注</text>
</view>
<view class="textarea-container">
<uv-textarea
v-model="finalRemark"
placeholder="请填写备注"
:maxlength="200"
:show-confirm-bar="false"
height="60"
border="none"
:custom-style="{ backgroundColor: '#f5f5f5' }"
></uv-textarea>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-container">
<uv-button
type="primary"
text="立即提交"
:custom-style="{ backgroundColor: '#C70019', borderRadius: '25px' }"
@click="submitMaintenance"
></uv-button>
</view>
<!-- 日期选择器 -->
<uv-picker
confirm-color="#C70019"
ref="datePicker"
mode="date"
@confirm="confirmDate"
@cancel="cancelDate"
></uv-picker>
<uv-picker
confirm-color="#C70019"
ref="nextDatePicker"
mode="date"
@confirm="confirmNextDate"
@cancel="cancelNextDate"
></uv-picker>
</view>
</template>
<script>
export default {
data() {
return {
// 表单数据
maintainer: '',
maintenanceDate: '',
beforeStatus: '',
beforeImageList: [],
afterStatus: '',
afterImageList: [],
hasCost: null,
newCostName: '',
costList: [],
remark: '',
attachmentList: [],
nextMaintenanceDate: '',
finalRemark: ''
}
},
methods: {
// 显示日期选择器
showDatePicker() {
this.$refs.datePicker.open()
},
// 确认日期
confirmDate(e) {
this.maintenanceDate = e.value
},
// 取消日期选择
cancelDate() {
// 取消操作
},
// 显示下次保养日期选择器
showNextDatePicker() {
this.$refs.nextDatePicker.open()
},
// 确认下次保养日期
confirmNextDate(e) {
this.nextMaintenanceDate = e.value
},
// 取消下次保养日期选择
cancelNextDate() {
// 取消操作
},
// 选择是否产生费用
selectCost(value) {
this.hasCost = value
if (!value) {
this.costList = []
this.newCostName = ''
}
},
// 添加费用项目
addCostItem() {
if (!this.newCostName.trim()) {
uni.showToast({ title: '请输入费用名称', icon: 'none' })
return
}
this.costList.push({
name: this.newCostName,
quantity: 1,
amount: ''
})
this.newCostName = ''
},
// 删除费用项目
removeCostItem(index) {
if (this.costList.length > 1) {
this.costList.splice(index, 1)
} else {
uni.showToast({ title: '至少保留一个费用项目', icon: 'none' })
}
},
// 增加数量
increaseQuantity(index) {
this.costList[index].quantity++
},
// 减少数量
decreaseQuantity(index) {
if (this.costList[index].quantity > 1) {
this.costList[index].quantity--
}
},
// 上传保养前图片
async uploadBeforeImage() {
try {
const result = await this.$utils.chooseAndUpload()
if (result && result.success) {
console.log(result);
this.beforeImageList.push(result.url)
}
} catch (error) {
console.error('图片上传失败:', error)
uni.showToast({
title: '图片上传失败',
icon: 'error'
})
}
},
// 删除保养前图片
deleteBeforeImage(index) {
this.beforeImageList.splice(index, 1)
},
// 上传保养后图片
async uploadAfterImage() {
try {
const result = await this.$utils.chooseAndUpload()
if (result && result.success) {
console.log(result);
this.afterImageList.push(result.url)
}
} catch (error) {
console.error('头像上传失败:', error)
uni.showToast({
title: '头像上传失败',
icon: 'error'
})
}
},
// 删除保养后图片
deleteAfterImage(index) {
this.afterImageList.splice(index, 1)
},
// 上传附件
async uploadAttachment() {
try {
const result = await this.$utils.chooseAndUpload()
if (result && result.success) {
console.log(result);
this.attachmentList.push(result.url)
}
} catch (error) {
console.error('头像上传失败:', error)
uni.showToast({
title: '头像上传失败',
icon: 'error'
})
}
},
// 删除附件
deleteAttachment(index) {
this.attachmentList.splice(index, 1)
},
// 预览图片
previewImage(url, imageList) {
uni.previewImage({
urls: imageList,
current: url
})
},
// 聚焦输入框方法
focusMaintainer() {
this.$refs.maintainerInput.focus()
},
// 提交保养
submitMaintenance() {
// 表单验证
if (!this.maintainer.trim()) {
uni.showToast({ title: '请填写保养人', icon: 'none' })
return
}
if (!this.maintenanceDate) {
uni.showToast({ title: '请选择保养日期', icon: 'none' })
return
}
if (!this.beforeStatus.trim()) {
uni.showToast({ title: '请填写保养前状态', icon: 'none' })
return
}
if (!this.afterStatus.trim()) {
uni.showToast({ title: '请填写保养后状态', icon: 'none' })
return
}
// 提交数据
const formData = {
maintainer: this.maintainer,
maintenanceDate: this.maintenanceDate,
beforeStatus: this.beforeStatus,
beforeImageList: this.beforeImageList,
afterStatus: this.afterStatus,
afterImageList: this.afterImageList,
hasCost: this.hasCost,
costList: this.costList,
remark: this.remark,
attachmentList: this.attachmentList,
nextMaintenanceDate: this.nextMaintenanceDate,
finalRemark: this.finalRemark
}
console.log('提交数据:', formData)
uni.showToast({ title: '提交成功', icon: 'success' })
// 返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
}
}
</script>
<style lang="scss" scoped>
.maintenance-submit {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 200rpx;
}
.maintenance-info {
margin: 18rpx;
background: #ffffff;
border-radius: 15rpx;
box-shadow: 0rpx 3rpx 6rpx 0rpx rgba(0,0,0,0.16);
padding: 40rpx;
.info-header {
display: flex;
align-items: center;
margin-bottom: 40rpx;
.red-line {
width: 9rpx;
height: 33rpx;
background-color: $primary-color;
margin-right: 7rpx;
border-radius: 5rpx;
}
.info-title {
font-size: 30rpx;
font-weight: bold;
color: $primary-text-color;
}
}
.form-item-header {
border-bottom: none;
margin-top: 20rpx;
}
.form-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 0;
border-bottom: 2rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.label {
font-size: 30rpx;
color: $primary-text-color;
flex-shrink: 0;
&.active {
font-weight: bold;
}
}
.value {
font-size: 30rpx;
color: $secondary-text-color;
&.placeholder {
color: $secondary-text-color;
}
}
.select-area {
display: flex;
align-items: center;
gap: 16rpx;
}
.input-area {
flex: 1;
// background-color: #f5f5f5;
border-radius: 8rpx;
padding: 16rpx 24rpx;
margin-left: 24rpx;
.input-field {
width: 100%;
font-size: 30rpx;
color: $primary-text-color;
background: transparent;
border: none;
outline: none;
text-align: right;
&::placeholder {
color: $secondary-text-color;
}
}
}
.radio-options {
display: flex;
gap: 60rpx;
.radio-item {
display: flex;
align-items: center;
gap: 16rpx;
.radio-dot {
width: 32rpx;
height: 32rpx;
border: 4rpx solid #ddd;
border-radius: 50%;
position: relative;
&.active {
border-color: $primary-color;
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 16rpx;
height: 16rpx;
background-color: $primary-color;
border-radius: 50%;
}
}
}
text {
font-size: 30rpx;
color: $secondary-text-color;
&.active {
color: $primary-color;
}
}
}
}
}
.textarea-container {
border-radius: 8rpx;
}
.image-upload {
display: flex;
flex-wrap: wrap;
gap: 24rpx;
margin: 16rpx 0;
.upload-btn {
width: 160rpx;
height: 160rpx;
border: 2rpx dashed $primary-color;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
}
.image-item {
position: relative;
width: 160rpx;
height: 160rpx;
image {
width: 100%;
height: 100%;
border-radius: 8rpx;
}
.delete-btn {
position: absolute;
top: -12rpx;
right: -12rpx;
width: 40rpx;
height: 40rpx;
background-color: #ff4757;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
}
}
.cost-section {
.cost-input {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 0;
border-bottom: 2rpx solid #f0f0f0;
.label {
font-size: 30rpx;
color: $primary-text-color;
flex-shrink: 0;
margin-right: 24rpx;
}
}
.cost-list {
.cost-header {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 4rpx solid #f0f0f0;
.header-item {
flex: 1;
font-size: 28rpx;
font-weight: bold;
color: $primary-text-color;
text-align: center;
&:first-child {
text-align: left;
}
}
}
.cost-item {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 2rpx solid #f0f0f0;
gap: 24rpx;
.cost-name {
flex: 1;
font-size: 28rpx;
color: $primary-text-color;
}
.quantity-control {
display: flex;
align-items: center;
gap: 16rpx;
.quantity {
font-size: 28rpx;
color: $primary-text-color;
min-width: 40rpx;
text-align: center;
}
}
}
.add-cost-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
padding: 24rpx 0;
color: $primary-color;
text {
font-size: 28rpx;
}
}
}
}
}
.submit-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 32rpx;
background-color: #fff;
border-top: 2rpx solid #f0f0f0;
}
</style>