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

783 lines
20 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" >
  13. <input
  14. v-model="maintenanceName"
  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="stateFrontText"
  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 stateFrontImage" :key="index" class="image-item">
  47. <image :src="img" mode="aspectFill" @click="previewImage(img, stateFrontImage)"></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="stateBackText"
  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 stateBackImage" :key="index" class="image-item">
  74. <image :src="img" mode="aspectFill" @click="previewImage(img, stateBackImage)"></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: isExpend === true }"
  90. @click="selectCost(true)"
  91. >
  92. <view class="radio-dot" :class="{ active: isExpend === true }"></view>
  93. <text :class="{ active: isExpend === true }"></text>
  94. </view>
  95. <view
  96. class="radio-item"
  97. :class="{ active: isExpend === false }"
  98. @click="selectCost(false)"
  99. >
  100. <view class="radio-dot" :class="{ active: isExpend === false }"></view>
  101. <text :class="{ active: isExpend === false }"></text>
  102. </view>
  103. </view>
  104. </view>
  105. <!-- 产生费用 -->
  106. <view v-if="isExpend" 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="remarkText"
  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 remarkImage" :key="index" class="image-item">
  184. <image :src="img" mode="aspectFill" @click="previewImage(img, remarkImage)"></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="remark"
  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. :disabled="submiting"
  223. :custom-style="{ backgroundColor: '#C70019', borderRadius: '25px' }"
  224. @click="submitMaintenance"
  225. ></uv-button>
  226. </view>
  227. <!-- 日期选择器 -->
  228. <uv-picker
  229. confirm-color="#C70019"
  230. ref="datePicker"
  231. mode="date"
  232. @confirm="confirmDate"
  233. @cancel="cancelDate"
  234. ></uv-picker>
  235. <uv-picker
  236. confirm-color="#C70019"
  237. ref="nextDatePicker"
  238. mode="date"
  239. @confirm="confirmNextDate"
  240. @cancel="cancelNextDate"
  241. ></uv-picker>
  242. </view>
  243. </template>
  244. <script>
  245. export default {
  246. data() {
  247. return {
  248. // 表单数据
  249. maintenanceName: '',
  250. maintenanceDate: '',
  251. stateFrontText: '',
  252. stateFrontImage: [],
  253. stateBackText: '',
  254. stateBackImage: [],
  255. isExpend: false,
  256. newCostName: '',
  257. costList: [],
  258. remarkText: '',
  259. remarkImage: [],
  260. nextMaintenanceDate: '',
  261. remark: '',
  262. showpieceId: '',
  263. submiting: false
  264. }
  265. },
  266. computed: {
  267. // 计算总金额
  268. amount() {
  269. return this.costList.reduce((sum, item) => sum + (parseFloat(item.amount) || 0), 0)
  270. }
  271. },
  272. methods: {
  273. // 显示日期选择器
  274. showDatePicker() {
  275. this.$refs.datePicker.open()
  276. },
  277. // 确认日期
  278. confirmDate(e) {
  279. this.maintenanceDate = e.value
  280. },
  281. // 取消日期选择
  282. cancelDate() {
  283. // 取消操作
  284. },
  285. // 显示下次保养日期选择器
  286. showNextDatePicker() {
  287. this.$refs.nextDatePicker.open()
  288. },
  289. // 确认下次保养日期
  290. confirmNextDate(e) {
  291. this.nextMaintenanceDate = e.value
  292. },
  293. // 取消下次保养日期选择
  294. cancelNextDate() {
  295. // 取消操作
  296. },
  297. // 选择是否产生费用
  298. selectCost(value) {
  299. this.isExpend = value
  300. if (!value) {
  301. this.costList = []
  302. this.newCostName = ''
  303. }
  304. },
  305. // 添加费用项目
  306. addCostItem() {
  307. if (!this.newCostName.trim()) {
  308. uni.showToast({ title: '请输入费用名称', icon: 'none' })
  309. return
  310. }
  311. this.costList.push({
  312. name: this.newCostName,
  313. quantity: 1,
  314. amount: ''
  315. })
  316. this.newCostName = ''
  317. },
  318. // 删除费用项目
  319. removeCostItem(index) {
  320. if (this.costList.length > 1) {
  321. this.costList.splice(index, 1)
  322. } else {
  323. uni.showToast({ title: '至少保留一个费用项目', icon: 'none' })
  324. }
  325. },
  326. // 增加数量
  327. increaseQuantity(index) {
  328. this.costList[index].quantity++
  329. },
  330. // 减少数量
  331. decreaseQuantity(index) {
  332. if (this.costList[index].quantity > 1) {
  333. this.costList[index].quantity--
  334. }
  335. },
  336. // 上传保养前图片
  337. async uploadBeforeImage() {
  338. try {
  339. const result = await this.$utils.chooseAndUpload()
  340. if (result && result.success) {
  341. console.log(result);
  342. this.stateFrontImage.push(result.url)
  343. }
  344. } catch (error) {
  345. console.error('图片上传失败:', error)
  346. uni.showToast({
  347. title: '图片上传失败',
  348. icon: 'error'
  349. })
  350. }
  351. },
  352. // 删除保养前图片
  353. deleteBeforeImage(index) {
  354. this.stateFrontImage.splice(index, 1)
  355. },
  356. // 上传保养后图片
  357. async uploadAfterImage() {
  358. try {
  359. const result = await this.$utils.chooseAndUpload()
  360. if (result && result.success) {
  361. console.log(result);
  362. this.stateBackImage.push(result.url)
  363. }
  364. } catch (error) {
  365. console.error('头像上传失败:', error)
  366. uni.showToast({
  367. title: '头像上传失败',
  368. icon: 'error'
  369. })
  370. }
  371. },
  372. // 删除保养后图片
  373. deleteAfterImage(index) {
  374. this.stateBackImage.splice(index, 1)
  375. },
  376. // 上传附件
  377. async uploadAttachment() {
  378. try {
  379. const result = await this.$utils.chooseAndUpload()
  380. if (result && result.success) {
  381. console.log(result);
  382. this.remarkImage.push(result.url)
  383. }
  384. } catch (error) {
  385. console.error('头像上传失败:', error)
  386. uni.showToast({
  387. title: '头像上传失败',
  388. icon: 'error'
  389. })
  390. }
  391. },
  392. // 删除附件
  393. deleteAttachment(index) {
  394. this.remarkImage.splice(index, 1)
  395. },
  396. // 预览图片
  397. previewImage(url, imageList) {
  398. uni.previewImage({
  399. urls: imageList,
  400. current: url
  401. })
  402. },
  403. // 提交保养
  404. async submitMaintenance() {
  405. // 表单验证
  406. if (!this.maintenanceName.trim()) {
  407. uni.showToast({ title: '请填写保养人', icon: 'none' })
  408. return
  409. }
  410. // 先去掉日期限制
  411. // if (!this.maintenanceDate) {
  412. // uni.showToast({ title: '请选择保养日期', icon: 'none' })
  413. // return
  414. // }
  415. if (!this.stateFrontText.trim()) {
  416. uni.showToast({ title: '请填写保养前状态', icon: 'none' })
  417. return
  418. }
  419. if (!this.stateBackText.trim()) {
  420. uni.showToast({ title: '请填写保养后状态', icon: 'none' })
  421. return
  422. }
  423. // 提交数据
  424. const formData = {
  425. maintenanceName: this.maintenanceName,
  426. maintenanceDate: this.maintenanceDate,
  427. stateFrontText: this.stateFrontText,
  428. stateFrontImage: this.stateFrontImage?.join(',') || '',
  429. stateBackText: this.stateBackText,
  430. stateBackImage: this.stateBackImage?.join(',') || '',
  431. isExpend: this.isExpend,
  432. amount: this.amount,
  433. remarkText: this.remarkText,
  434. remarkImage: this.remarkImage?.join(',') || '',
  435. nextMaintenanceDate: this.nextMaintenanceDate,
  436. remark: this.remark,
  437. showpieceId: this.showpieceId
  438. }
  439. this.submiting = true
  440. const subRes = await this.$api.exhibit.addMaintenance(formData)
  441. if(subRes.code == 200){
  442. uni.showToast({ title: subRes.message, icon: 'success' })
  443. // 返回上一页
  444. setTimeout(() => {
  445. uni.navigateBack()
  446. }, 1000)
  447. }else{
  448. uni.showToast({ title: subRes.message, icon: 'none' })
  449. }
  450. this.submiting = false
  451. }
  452. },
  453. onLoad(options) {
  454. this.showpieceId = options.id
  455. }
  456. }
  457. </script>
  458. <style lang="scss" scoped>
  459. .maintenance-submit {
  460. min-height: 100vh;
  461. background-color: #f5f5f5;
  462. padding-bottom: 200rpx;
  463. }
  464. .maintenance-info {
  465. margin: 18rpx;
  466. background: #ffffff;
  467. border-radius: 15rpx;
  468. box-shadow: 0rpx 3rpx 6rpx 0rpx rgba(0,0,0,0.16);
  469. padding: 40rpx;
  470. .info-header {
  471. display: flex;
  472. align-items: center;
  473. margin-bottom: 40rpx;
  474. .red-line {
  475. width: 9rpx;
  476. height: 33rpx;
  477. background-color: $primary-color;
  478. margin-right: 7rpx;
  479. border-radius: 5rpx;
  480. }
  481. .info-title {
  482. font-size: 30rpx;
  483. font-weight: bold;
  484. color: $primary-text-color;
  485. }
  486. }
  487. .form-item-header {
  488. border-bottom: none;
  489. margin-top: 20rpx;
  490. }
  491. .form-item {
  492. display: flex;
  493. align-items: center;
  494. justify-content: space-between;
  495. padding: 24rpx 0;
  496. border-bottom: 2rpx solid #f0f0f0;
  497. &:last-child {
  498. border-bottom: none;
  499. }
  500. .label {
  501. font-size: 30rpx;
  502. color: $primary-text-color;
  503. flex-shrink: 0;
  504. &.active {
  505. font-weight: bold;
  506. }
  507. }
  508. .value {
  509. font-size: 30rpx;
  510. color: $secondary-text-color;
  511. &.placeholder {
  512. color: $secondary-text-color;
  513. }
  514. }
  515. .select-area {
  516. display: flex;
  517. align-items: center;
  518. gap: 16rpx;
  519. }
  520. .input-area {
  521. flex: 1;
  522. // background-color: #f5f5f5;
  523. border-radius: 8rpx;
  524. padding: 16rpx 24rpx;
  525. margin-left: 24rpx;
  526. .input-field {
  527. width: 100%;
  528. font-size: 30rpx;
  529. color: $primary-text-color;
  530. background: transparent;
  531. border: none;
  532. outline: none;
  533. text-align: right;
  534. &::placeholder {
  535. color: $secondary-text-color;
  536. }
  537. }
  538. }
  539. .radio-options {
  540. display: flex;
  541. gap: 60rpx;
  542. .radio-item {
  543. display: flex;
  544. align-items: center;
  545. gap: 16rpx;
  546. .radio-dot {
  547. width: 32rpx;
  548. height: 32rpx;
  549. border: 4rpx solid #ddd;
  550. border-radius: 50%;
  551. position: relative;
  552. &.active {
  553. border-color: $primary-color;
  554. &::after {
  555. content: '';
  556. position: absolute;
  557. top: 50%;
  558. left: 50%;
  559. transform: translate(-50%, -50%);
  560. width: 16rpx;
  561. height: 16rpx;
  562. background-color: $primary-color;
  563. border-radius: 50%;
  564. }
  565. }
  566. }
  567. text {
  568. font-size: 30rpx;
  569. color: $secondary-text-color;
  570. &.active {
  571. color: $primary-color;
  572. }
  573. }
  574. }
  575. }
  576. }
  577. .textarea-container {
  578. border-radius: 8rpx;
  579. }
  580. .image-upload {
  581. display: flex;
  582. flex-wrap: wrap;
  583. gap: 24rpx;
  584. margin: 16rpx 0;
  585. .upload-btn {
  586. width: 160rpx;
  587. height: 160rpx;
  588. border: 2rpx dashed $primary-color;
  589. display: flex;
  590. align-items: center;
  591. justify-content: center;
  592. background-color: #fff;
  593. }
  594. .image-item {
  595. position: relative;
  596. width: 160rpx;
  597. height: 160rpx;
  598. image {
  599. width: 100%;
  600. height: 100%;
  601. border-radius: 8rpx;
  602. }
  603. .delete-btn {
  604. position: absolute;
  605. top: -12rpx;
  606. right: -12rpx;
  607. width: 40rpx;
  608. height: 40rpx;
  609. background-color: #ff4757;
  610. border-radius: 50%;
  611. display: flex;
  612. align-items: center;
  613. justify-content: center;
  614. }
  615. }
  616. }
  617. .cost-section {
  618. .cost-input {
  619. display: flex;
  620. align-items: center;
  621. justify-content: space-between;
  622. padding: 24rpx 0;
  623. border-bottom: 2rpx solid #f0f0f0;
  624. .label {
  625. font-size: 30rpx;
  626. color: $primary-text-color;
  627. flex-shrink: 0;
  628. margin-right: 24rpx;
  629. }
  630. }
  631. .cost-list {
  632. .cost-header {
  633. display: flex;
  634. align-items: center;
  635. padding: 24rpx 0;
  636. border-bottom: 4rpx solid #f0f0f0;
  637. .header-item {
  638. flex: 1;
  639. font-size: 28rpx;
  640. font-weight: bold;
  641. color: $primary-text-color;
  642. text-align: center;
  643. &:first-child {
  644. text-align: left;
  645. }
  646. }
  647. }
  648. .cost-item {
  649. display: flex;
  650. align-items: center;
  651. padding: 24rpx 0;
  652. border-bottom: 2rpx solid #f0f0f0;
  653. gap: 24rpx;
  654. .cost-name {
  655. flex: 1;
  656. font-size: 28rpx;
  657. color: $primary-text-color;
  658. }
  659. .quantity-control {
  660. display: flex;
  661. align-items: center;
  662. gap: 16rpx;
  663. .quantity {
  664. font-size: 28rpx;
  665. color: $primary-text-color;
  666. min-width: 40rpx;
  667. text-align: center;
  668. }
  669. }
  670. }
  671. .add-cost-btn {
  672. display: flex;
  673. align-items: center;
  674. justify-content: center;
  675. gap: 16rpx;
  676. padding: 24rpx 0;
  677. color: $primary-color;
  678. text {
  679. font-size: 28rpx;
  680. }
  681. }
  682. }
  683. }
  684. }
  685. .submit-container {
  686. position: fixed;
  687. bottom: 0;
  688. left: 0;
  689. right: 0;
  690. padding: 32rpx;
  691. background-color: #fff;
  692. border-top: 2rpx solid #f0f0f0;
  693. }
  694. </style>