展品维保小程序前端代码接口
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.

767 lines
19 KiB

  1. <template>
  2. <view class="maintenance-submit">
  3. <!-- 保养基本信息 -->
  4. <view class="maintenance-info">
  5. <view class="info-header">
  6. <view class="red-line"></view>
  7. <text class="info-title">保养项目</text>
  8. </view>
  9. <!-- 保养人 -->
  10. <view class="form-item">
  11. <text class="label">保养人</text>
  12. <view class="input-area" @click="focusMaintainer">
  13. <input
  14. v-model="maintainer"
  15. placeholder="请填写"
  16. class="input-field"
  17. ref="maintainerInput"
  18. />
  19. </view>
  20. </view>
  21. <!-- 保养日期 -->
  22. <view class="form-item" @click="showDatePicker">
  23. <text class="label">保养日期</text>
  24. <view class="select-area">
  25. <text class="value" :class="{ placeholder: !maintenanceDate }">{{ maintenanceDate || '请选择' }}</text>
  26. <uv-icon name="arrow-down" size="18" color="#000"></uv-icon>
  27. </view>
  28. </view>
  29. <!-- 保养前状态 -->
  30. <view class="form-item">
  31. <text class="label">保养前状态</text>
  32. </view>
  33. <view class="textarea-container">
  34. <uv-textarea
  35. v-model="beforeStatus"
  36. placeholder="请填写保养前的设备内容"
  37. :maxlength="200"
  38. :show-confirm-bar="false"
  39. height="60"
  40. border="none"
  41. :custom-style="{ backgroundColor: '#f5f5f5' }"
  42. ></uv-textarea>
  43. </view>
  44. <!-- 保养前图片 -->
  45. <view class="image-upload">
  46. <view v-for="(img, index) in beforeImageList" :key="index" class="image-item">
  47. <image :src="img" mode="aspectFill" @click="previewImage(img, beforeImageList)"></image>
  48. <view class="delete-btn" @click="deleteBeforeImage(index)">
  49. <uv-icon name="close" size="12" color="#fff"></uv-icon>
  50. </view>
  51. </view>
  52. <view class="upload-btn" @click="uploadBeforeImage">
  53. <uv-icon name="camera" size="34" color="#C70019"></uv-icon>
  54. </view>
  55. </view>
  56. <!-- 保养后状态 -->
  57. <view class="form-item">
  58. <text class="label">保养后状态</text>
  59. </view>
  60. <view class="textarea-container">
  61. <uv-textarea
  62. v-model="afterStatus"
  63. placeholder="请填写保养后的设备内容"
  64. :maxlength="200"
  65. :show-confirm-bar="false"
  66. height="60"
  67. border="none"
  68. :custom-style="{ backgroundColor: '#f5f5f5' }"
  69. ></uv-textarea>
  70. </view>
  71. <!-- 保养后图片 -->
  72. <view class="image-upload">
  73. <view v-for="(img, index) in afterImageList" :key="index" class="image-item">
  74. <image :src="img" mode="aspectFill" @click="previewImage(img, afterImageList)"></image>
  75. <view class="delete-btn" @click="deleteAfterImage(index)">
  76. <uv-icon name="close" size="12" color="#fff"></uv-icon>
  77. </view>
  78. </view>
  79. <view class="upload-btn" @click="uploadAfterImage">
  80. <uv-icon name="camera" size="34" color="#C70019"></uv-icon>
  81. </view>
  82. </view>
  83. <!-- 是否产生费用 -->
  84. <view class="form-item">
  85. <text class="label">是否产生费用</text>
  86. <view class="radio-options">
  87. <view
  88. class="radio-item"
  89. :class="{ active: hasCost === true }"
  90. @click="selectCost(true)"
  91. >
  92. <view class="radio-dot" :class="{ active: hasCost === true }"></view>
  93. <text :class="{ active: hasCost === true }"></text>
  94. </view>
  95. <view
  96. class="radio-item"
  97. :class="{ active: hasCost === false }"
  98. @click="selectCost(false)"
  99. >
  100. <view class="radio-dot" :class="{ active: hasCost === false }"></view>
  101. <text :class="{ active: hasCost === false }"></text>
  102. </view>
  103. </view>
  104. </view>
  105. <!-- 产生费用 -->
  106. <view v-if="hasCost" class="cost-section">
  107. <view class="form-item form-item-header">
  108. <text class="label active">产生费用</text>
  109. </view>
  110. <view class="cost-input">
  111. <text class="label">费用名称</text>
  112. <uv-input
  113. v-model="newCostName"
  114. placeholder="请输入费用"
  115. border="none"
  116. :custom-style="{ backgroundColor: '#fff', borderRadius: '4px', textAlign: 'right', marginLeft: '24rpx' }"
  117. ></uv-input>
  118. </view>
  119. <view class="cost-list">
  120. <view class="cost-header">
  121. <text class="header-item">费用名称</text>
  122. <text class="header-item">数量</text>
  123. <text class="header-item">金额</text>
  124. </view>
  125. <view v-for="(item, index) in costList" :key="index" class="cost-item">
  126. <text class="cost-name">{{ item.name }}</text>
  127. <view class="quantity-control">
  128. <uv-icon
  129. name="minus-circle"
  130. size="20"
  131. :color="item.quantity > 1 ? '#C70019' : '#ccc'"
  132. @click="decreaseQuantity(index)"
  133. ></uv-icon>
  134. <text class="quantity">{{ item.quantity }}</text>
  135. <uv-icon
  136. name="plus-circle"
  137. size="20"
  138. color="#C70019"
  139. @click="increaseQuantity(index)"
  140. ></uv-icon>
  141. </view>
  142. <uv-input
  143. v-model="item.amount"
  144. placeholder="0.00"
  145. type="digit"
  146. border="none"
  147. :custom-style="{ backgroundColor: '#f5f5f5', borderRadius: '4px', textAlign: 'right' }"
  148. ></uv-input>
  149. <uv-icon
  150. name="close-circle"
  151. size="20"
  152. color="#C70019"
  153. @click="removeCostItem(index)"
  154. ></uv-icon>
  155. </view>
  156. <view class="add-cost-btn" @click="addCostItem">
  157. <uv-icon name="plus-circle" size="20" color="#C70019"></uv-icon>
  158. <text>添加费用项目</text>
  159. </view>
  160. </view>
  161. </view>
  162. <!-- 附件信息 -->
  163. <view class="form-item form-item-header">
  164. <text class="label active">附件信息</text>
  165. </view>
  166. <!-- 保养备注 -->
  167. <view class="form-item">
  168. <text class="label">保养备注</text>
  169. </view>
  170. <view class="textarea-container">
  171. <uv-textarea
  172. v-model="remark"
  173. placeholder="请填写备注"
  174. :maxlength="200"
  175. :show-confirm-bar="false"
  176. height="60"
  177. border="none"
  178. :custom-style="{ backgroundColor: '#f5f5f5' }"
  179. ></uv-textarea>
  180. </view>
  181. <!-- 附件图片 -->
  182. <view class="image-upload">
  183. <view v-for="(img, index) in attachmentList" :key="index" class="image-item">
  184. <image :src="img" mode="aspectFill" @click="previewImage(img, attachmentList)"></image>
  185. <view class="delete-btn" @click="deleteAttachment(index)">
  186. <uv-icon name="close" size="12" color="#fff"></uv-icon>
  187. </view>
  188. </view>
  189. <view class="upload-btn" @click="uploadAttachment">
  190. <uv-icon name="camera" size="34" color="#C70019"></uv-icon>
  191. </view>
  192. </view>
  193. <!-- 下次保养日期 -->
  194. <view class="form-item" @click="showNextDatePicker">
  195. <text class="label">下次保养日期</text>
  196. <view class="select-area">
  197. <text class="value" :class="{ placeholder: !nextMaintenanceDate }">{{ nextMaintenanceDate || '请选择' }}</text>
  198. <uv-icon name="arrow-down" size="18" color="#000"></uv-icon>
  199. </view>
  200. </view>
  201. <!-- 备注 -->
  202. <view class="form-item">
  203. <text class="label">备注</text>
  204. </view>
  205. <view class="textarea-container">
  206. <uv-textarea
  207. v-model="finalRemark"
  208. placeholder="请填写备注"
  209. :maxlength="200"
  210. :show-confirm-bar="false"
  211. height="60"
  212. border="none"
  213. :custom-style="{ backgroundColor: '#f5f5f5' }"
  214. ></uv-textarea>
  215. </view>
  216. </view>
  217. <!-- 提交按钮 -->
  218. <view class="submit-container">
  219. <uv-button
  220. type="primary"
  221. text="立即提交"
  222. :custom-style="{ backgroundColor: '#C70019', borderRadius: '25px' }"
  223. @click="submitMaintenance"
  224. ></uv-button>
  225. </view>
  226. <!-- 日期选择器 -->
  227. <uv-picker
  228. confirm-color="#C70019"
  229. ref="datePicker"
  230. mode="date"
  231. @confirm="confirmDate"
  232. @cancel="cancelDate"
  233. ></uv-picker>
  234. <uv-picker
  235. confirm-color="#C70019"
  236. ref="nextDatePicker"
  237. mode="date"
  238. @confirm="confirmNextDate"
  239. @cancel="cancelNextDate"
  240. ></uv-picker>
  241. </view>
  242. </template>
  243. <script>
  244. export default {
  245. data() {
  246. return {
  247. // 表单数据
  248. maintainer: '',
  249. maintenanceDate: '',
  250. beforeStatus: '',
  251. beforeImageList: [],
  252. afterStatus: '',
  253. afterImageList: [],
  254. hasCost: null,
  255. newCostName: '',
  256. costList: [],
  257. remark: '',
  258. attachmentList: [],
  259. nextMaintenanceDate: '',
  260. finalRemark: ''
  261. }
  262. },
  263. methods: {
  264. // 显示日期选择器
  265. showDatePicker() {
  266. this.$refs.datePicker.open()
  267. },
  268. // 确认日期
  269. confirmDate(e) {
  270. this.maintenanceDate = e.value
  271. },
  272. // 取消日期选择
  273. cancelDate() {
  274. // 取消操作
  275. },
  276. // 显示下次保养日期选择器
  277. showNextDatePicker() {
  278. this.$refs.nextDatePicker.open()
  279. },
  280. // 确认下次保养日期
  281. confirmNextDate(e) {
  282. this.nextMaintenanceDate = e.value
  283. },
  284. // 取消下次保养日期选择
  285. cancelNextDate() {
  286. // 取消操作
  287. },
  288. // 选择是否产生费用
  289. selectCost(value) {
  290. this.hasCost = value
  291. if (!value) {
  292. this.costList = []
  293. this.newCostName = ''
  294. }
  295. },
  296. // 添加费用项目
  297. addCostItem() {
  298. if (!this.newCostName.trim()) {
  299. uni.showToast({ title: '请输入费用名称', icon: 'none' })
  300. return
  301. }
  302. this.costList.push({
  303. name: this.newCostName,
  304. quantity: 1,
  305. amount: ''
  306. })
  307. this.newCostName = ''
  308. },
  309. // 删除费用项目
  310. removeCostItem(index) {
  311. if (this.costList.length > 1) {
  312. this.costList.splice(index, 1)
  313. } else {
  314. uni.showToast({ title: '至少保留一个费用项目', icon: 'none' })
  315. }
  316. },
  317. // 增加数量
  318. increaseQuantity(index) {
  319. this.costList[index].quantity++
  320. },
  321. // 减少数量
  322. decreaseQuantity(index) {
  323. if (this.costList[index].quantity > 1) {
  324. this.costList[index].quantity--
  325. }
  326. },
  327. // 上传保养前图片
  328. async uploadBeforeImage() {
  329. try {
  330. const result = await this.$utils.chooseAndUpload()
  331. if (result && result.success) {
  332. console.log(result);
  333. this.beforeImageList.push(result.url)
  334. }
  335. } catch (error) {
  336. console.error('图片上传失败:', error)
  337. uni.showToast({
  338. title: '图片上传失败',
  339. icon: 'error'
  340. })
  341. }
  342. },
  343. // 删除保养前图片
  344. deleteBeforeImage(index) {
  345. this.beforeImageList.splice(index, 1)
  346. },
  347. // 上传保养后图片
  348. async uploadAfterImage() {
  349. try {
  350. const result = await this.$utils.chooseAndUpload()
  351. if (result && result.success) {
  352. console.log(result);
  353. this.afterImageList.push(result.url)
  354. }
  355. } catch (error) {
  356. console.error('头像上传失败:', error)
  357. uni.showToast({
  358. title: '头像上传失败',
  359. icon: 'error'
  360. })
  361. }
  362. },
  363. // 删除保养后图片
  364. deleteAfterImage(index) {
  365. this.afterImageList.splice(index, 1)
  366. },
  367. // 上传附件
  368. async uploadAttachment() {
  369. try {
  370. const result = await this.$utils.chooseAndUpload()
  371. if (result && result.success) {
  372. console.log(result);
  373. this.attachmentList.push(result.url)
  374. }
  375. } catch (error) {
  376. console.error('头像上传失败:', error)
  377. uni.showToast({
  378. title: '头像上传失败',
  379. icon: 'error'
  380. })
  381. }
  382. },
  383. // 删除附件
  384. deleteAttachment(index) {
  385. this.attachmentList.splice(index, 1)
  386. },
  387. // 预览图片
  388. previewImage(url, imageList) {
  389. uni.previewImage({
  390. urls: imageList,
  391. current: url
  392. })
  393. },
  394. // 聚焦输入框方法
  395. focusMaintainer() {
  396. this.$refs.maintainerInput.focus()
  397. },
  398. // 提交保养
  399. submitMaintenance() {
  400. // 表单验证
  401. if (!this.maintainer.trim()) {
  402. uni.showToast({ title: '请填写保养人', icon: 'none' })
  403. return
  404. }
  405. if (!this.maintenanceDate) {
  406. uni.showToast({ title: '请选择保养日期', icon: 'none' })
  407. return
  408. }
  409. if (!this.beforeStatus.trim()) {
  410. uni.showToast({ title: '请填写保养前状态', icon: 'none' })
  411. return
  412. }
  413. if (!this.afterStatus.trim()) {
  414. uni.showToast({ title: '请填写保养后状态', icon: 'none' })
  415. return
  416. }
  417. // 提交数据
  418. const formData = {
  419. maintainer: this.maintainer,
  420. maintenanceDate: this.maintenanceDate,
  421. beforeStatus: this.beforeStatus,
  422. beforeImageList: this.beforeImageList,
  423. afterStatus: this.afterStatus,
  424. afterImageList: this.afterImageList,
  425. hasCost: this.hasCost,
  426. costList: this.costList,
  427. remark: this.remark,
  428. attachmentList: this.attachmentList,
  429. nextMaintenanceDate: this.nextMaintenanceDate,
  430. finalRemark: this.finalRemark
  431. }
  432. console.log('提交数据:', formData)
  433. uni.showToast({ title: '提交成功', icon: 'success' })
  434. // 返回上一页
  435. setTimeout(() => {
  436. uni.navigateBack()
  437. }, 1500)
  438. }
  439. }
  440. }
  441. </script>
  442. <style lang="scss" scoped>
  443. .maintenance-submit {
  444. min-height: 100vh;
  445. background-color: #f5f5f5;
  446. padding-bottom: 200rpx;
  447. }
  448. .maintenance-info {
  449. margin: 18rpx;
  450. background: #ffffff;
  451. border-radius: 15rpx;
  452. box-shadow: 0rpx 3rpx 6rpx 0rpx rgba(0,0,0,0.16);
  453. padding: 40rpx;
  454. .info-header {
  455. display: flex;
  456. align-items: center;
  457. margin-bottom: 40rpx;
  458. .red-line {
  459. width: 9rpx;
  460. height: 33rpx;
  461. background-color: $primary-color;
  462. margin-right: 7rpx;
  463. border-radius: 5rpx;
  464. }
  465. .info-title {
  466. font-size: 30rpx;
  467. font-weight: bold;
  468. color: $primary-text-color;
  469. }
  470. }
  471. .form-item-header {
  472. border-bottom: none;
  473. margin-top: 20rpx;
  474. }
  475. .form-item {
  476. display: flex;
  477. align-items: center;
  478. justify-content: space-between;
  479. padding: 24rpx 0;
  480. border-bottom: 2rpx solid #f0f0f0;
  481. &:last-child {
  482. border-bottom: none;
  483. }
  484. .label {
  485. font-size: 30rpx;
  486. color: $primary-text-color;
  487. flex-shrink: 0;
  488. &.active {
  489. font-weight: bold;
  490. }
  491. }
  492. .value {
  493. font-size: 30rpx;
  494. color: $secondary-text-color;
  495. &.placeholder {
  496. color: $secondary-text-color;
  497. }
  498. }
  499. .select-area {
  500. display: flex;
  501. align-items: center;
  502. gap: 16rpx;
  503. }
  504. .input-area {
  505. flex: 1;
  506. // background-color: #f5f5f5;
  507. border-radius: 8rpx;
  508. padding: 16rpx 24rpx;
  509. margin-left: 24rpx;
  510. .input-field {
  511. width: 100%;
  512. font-size: 30rpx;
  513. color: $primary-text-color;
  514. background: transparent;
  515. border: none;
  516. outline: none;
  517. text-align: right;
  518. &::placeholder {
  519. color: $secondary-text-color;
  520. }
  521. }
  522. }
  523. .radio-options {
  524. display: flex;
  525. gap: 60rpx;
  526. .radio-item {
  527. display: flex;
  528. align-items: center;
  529. gap: 16rpx;
  530. .radio-dot {
  531. width: 32rpx;
  532. height: 32rpx;
  533. border: 4rpx solid #ddd;
  534. border-radius: 50%;
  535. position: relative;
  536. &.active {
  537. border-color: $primary-color;
  538. &::after {
  539. content: '';
  540. position: absolute;
  541. top: 50%;
  542. left: 50%;
  543. transform: translate(-50%, -50%);
  544. width: 16rpx;
  545. height: 16rpx;
  546. background-color: $primary-color;
  547. border-radius: 50%;
  548. }
  549. }
  550. }
  551. text {
  552. font-size: 30rpx;
  553. color: $secondary-text-color;
  554. &.active {
  555. color: $primary-color;
  556. }
  557. }
  558. }
  559. }
  560. }
  561. .textarea-container {
  562. border-radius: 8rpx;
  563. }
  564. .image-upload {
  565. display: flex;
  566. flex-wrap: wrap;
  567. gap: 24rpx;
  568. margin: 16rpx 0;
  569. .upload-btn {
  570. width: 160rpx;
  571. height: 160rpx;
  572. border: 2rpx dashed $primary-color;
  573. display: flex;
  574. align-items: center;
  575. justify-content: center;
  576. background-color: #fff;
  577. }
  578. .image-item {
  579. position: relative;
  580. width: 160rpx;
  581. height: 160rpx;
  582. image {
  583. width: 100%;
  584. height: 100%;
  585. border-radius: 8rpx;
  586. }
  587. .delete-btn {
  588. position: absolute;
  589. top: -12rpx;
  590. right: -12rpx;
  591. width: 40rpx;
  592. height: 40rpx;
  593. background-color: #ff4757;
  594. border-radius: 50%;
  595. display: flex;
  596. align-items: center;
  597. justify-content: center;
  598. }
  599. }
  600. }
  601. .cost-section {
  602. .cost-input {
  603. display: flex;
  604. align-items: center;
  605. justify-content: space-between;
  606. padding: 24rpx 0;
  607. border-bottom: 2rpx solid #f0f0f0;
  608. .label {
  609. font-size: 30rpx;
  610. color: $primary-text-color;
  611. flex-shrink: 0;
  612. margin-right: 24rpx;
  613. }
  614. }
  615. .cost-list {
  616. .cost-header {
  617. display: flex;
  618. align-items: center;
  619. padding: 24rpx 0;
  620. border-bottom: 4rpx solid #f0f0f0;
  621. .header-item {
  622. flex: 1;
  623. font-size: 28rpx;
  624. font-weight: bold;
  625. color: $primary-text-color;
  626. text-align: center;
  627. &:first-child {
  628. text-align: left;
  629. }
  630. }
  631. }
  632. .cost-item {
  633. display: flex;
  634. align-items: center;
  635. padding: 24rpx 0;
  636. border-bottom: 2rpx solid #f0f0f0;
  637. gap: 24rpx;
  638. .cost-name {
  639. flex: 1;
  640. font-size: 28rpx;
  641. color: $primary-text-color;
  642. }
  643. .quantity-control {
  644. display: flex;
  645. align-items: center;
  646. gap: 16rpx;
  647. .quantity {
  648. font-size: 28rpx;
  649. color: $primary-text-color;
  650. min-width: 40rpx;
  651. text-align: center;
  652. }
  653. }
  654. }
  655. .add-cost-btn {
  656. display: flex;
  657. align-items: center;
  658. justify-content: center;
  659. gap: 16rpx;
  660. padding: 24rpx 0;
  661. color: $primary-color;
  662. text {
  663. font-size: 28rpx;
  664. }
  665. }
  666. }
  667. }
  668. }
  669. .submit-container {
  670. position: fixed;
  671. bottom: 0;
  672. left: 0;
  673. right: 0;
  674. padding: 32rpx;
  675. background-color: #fff;
  676. border-top: 2rpx solid #f0f0f0;
  677. }
  678. </style>