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

781 lines
20 KiB

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