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

808 lines
22 KiB

1 month 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 class="form-item" v-if="isExpend === '1'">
  107. <text class="label">产生费用</text>
  108. <view class="input-area" >
  109. <input
  110. v-model="amount"
  111. placeholder="请输入费用"
  112. disabled
  113. class="input-field"
  114. />
  115. </view>
  116. </view>
  117. <!-- 费用详情表格 -->
  118. <view class="cost-table" v-if="isExpend === '1'">
  119. <view class="table-header">
  120. <text class="header-cell">费用名称</text>
  121. <text class="header-cell">数量</text>
  122. <text class="header-cell">金额</text>
  123. <text class="header-cell"></text>
  124. </view>
  125. <view class="table-row" v-for="(item, index) in costList" :key="index">
  126. <view class="cell-input">
  127. <uv-input
  128. v-model="item.name"
  129. placeholder="费用名称"
  130. border="none"
  131. :custom-style="{ backgroundColor: 'transparent', fontSize: '28rpx' }"
  132. ></uv-input>
  133. </view>
  134. <view class="cell-input">
  135. <uv-input
  136. v-model="item.quantity"
  137. placeholder="数量"
  138. border="none"
  139. :custom-style="{ backgroundColor: 'transparent', fontSize: '28rpx' }"
  140. ></uv-input>
  141. </view>
  142. <view class="cell-input">
  143. <uv-input
  144. v-model="item.amount"
  145. placeholder="金额"
  146. type="digit"
  147. border="none"
  148. :custom-style="{ backgroundColor: 'transparent', fontSize: '28rpx' }"
  149. ></uv-input>
  150. </view>
  151. <view class="cell-action">
  152. <view class="action-btn delete-btn" @click="removeCostItem(index)" v-if="costList.length > 1">
  153. <uv-icon name="close" size="14" color="#fff"></uv-icon>
  154. </view>
  155. <view class="action-btn add-btn" @click="addCostItem" v-if="index === costList.length - 1">
  156. <uv-icon name="plus" size="14" color="#fff"></uv-icon>
  157. </view>
  158. </view>
  159. </view>
  160. </view>
  161. <!-- 附件信息 -->
  162. <view class="form-item form-item-header">
  163. <text class="label active">附件信息</text>
  164. </view>
  165. <!-- 保养备注 -->
  166. <view class="form-item">
  167. <text class="label">保养备注</text>
  168. </view>
  169. <view class="textarea-container">
  170. <uv-textarea
  171. v-model="remarkText"
  172. placeholder="请填写备注"
  173. :maxlength="200"
  174. :show-confirm-bar="false"
  175. height="60"
  176. border="none"
  177. :custom-style="{ backgroundColor: '#f5f5f5' }"
  178. ></uv-textarea>
  179. </view>
  180. <!-- 附件图片 -->
  181. <view class="image-upload">
  182. <view v-for="(img, index) in remarkImage" :key="index" class="image-item">
  183. <image :src="img" mode="aspectFill" @click="previewImage(img, remarkImage)"></image>
  184. <view class="delete-btn" @click="deleteAttachment(index)">
  185. <uv-icon name="close" size="12" color="#fff"></uv-icon>
  186. </view>
  187. </view>
  188. <view class="upload-btn" @click="uploadAttachment">
  189. <uv-icon name="camera" size="34" color="#C70019"></uv-icon>
  190. </view>
  191. </view>
  192. <!-- 下次保养日期 -->
  193. <view class="form-item" @click="showNextDatePicker">
  194. <text class="label">下次保养日期</text>
  195. <view class="select-area">
  196. <text class="value" :class="{ placeholder: !nextMaintenanceDate }">{{ nextMaintenanceDate || '请选择' }}</text>
  197. <uv-icon name="arrow-down" size="18" color="#000"></uv-icon>
  198. </view>
  199. </view>
  200. <!-- 备注 -->
  201. <view class="form-item">
  202. <text class="label">备注</text>
  203. </view>
  204. <view class="textarea-container">
  205. <uv-textarea
  206. v-model="remark"
  207. placeholder="请填写备注"
  208. :maxlength="200"
  209. :show-confirm-bar="false"
  210. height="60"
  211. border="none"
  212. :custom-style="{ backgroundColor: '#f5f5f5' }"
  213. ></uv-textarea>
  214. </view>
  215. </view>
  216. <!-- 提交按钮 -->
  217. <view class="submit-container">
  218. <uv-button
  219. type="primary"
  220. text="立即提交"
  221. :disabled="submiting"
  222. :custom-style="{ backgroundColor: '#C70019', borderRadius: '25px' }"
  223. @click="submitMaintenance"
  224. ></uv-button>
  225. </view>
  226. <!-- 日期选择器 -->
  227. <uv-datetime-picker
  228. confirm-color="#C70019"
  229. ref="datePicker"
  230. mode="date"
  231. v-model="timeValue"
  232. @confirm="confirmDate"
  233. ></uv-datetime-picker>
  234. <uv-datetime-picker
  235. confirm-color="#C70019"
  236. ref="nextDatePicker"
  237. mode="date"
  238. v-model="nextTimeValue"
  239. @confirm="confirmNextDate"
  240. ></uv-datetime-picker>
  241. </view>
  242. </template>
  243. <script>
  244. export default {
  245. data() {
  246. return {
  247. id: '',
  248. timeValue: Number(new Date()),
  249. nextTimeValue: Number(new Date()),
  250. // 表单数据
  251. maintenanceName: '',
  252. maintenanceDate: '',
  253. stateFrontText: '',
  254. stateFrontImage: [],
  255. stateBackText: '',
  256. stateBackImage: [],
  257. isExpend: '0',
  258. costList: [{ name: '', quantity: '', amount: '' }],
  259. remarkText: '',
  260. remarkImage: [],
  261. nextMaintenanceDate: '',
  262. remark: '',
  263. showpieceId: '',
  264. submiting: false
  265. }
  266. },
  267. computed: {
  268. // 计算总金额
  269. amount() {
  270. return this.costList.reduce((sum, item) => {
  271. return sum + (Number(item.quantity) * parseFloat(item.amount || 0))
  272. }, 0)
  273. }
  274. },
  275. methods: {
  276. // 显示日期选择器
  277. showDatePicker() {
  278. this.$refs.datePicker.open()
  279. },
  280. // 确认日期
  281. confirmDate(e) {
  282. // uv-datetime-picker返回的是时间戳,需要转换为日期格式
  283. this.maintenanceDate = this.$utils.formatTime(e.value)
  284. },
  285. // 显示下次保养日期选择器
  286. showNextDatePicker() {
  287. this.$refs.nextDatePicker.open()
  288. },
  289. // 确认下次保养日期
  290. confirmNextDate(e) {
  291. // uv-datetime-picker返回的是时间戳,需要转换为日期格式
  292. this.nextMaintenanceDate = this.$utils.formatTime(e.value)
  293. },
  294. // 选择是否产生费用
  295. selectCost(value) {
  296. this.isExpend = value
  297. if (value === '0') {
  298. this.costList = [{ name: '', quantity: '', amount: '' }]
  299. } else if (this.costList.length === 0) {
  300. this.costList = [{ name: '', quantity: '', amount: '' }]
  301. }
  302. },
  303. // 添加费用项目
  304. addCostItem() {
  305. this.costList.push({ name: '', quantity: '', amount: '' })
  306. },
  307. // 删除费用项目
  308. removeCostItem(index) {
  309. if (this.costList.length > 1) {
  310. this.costList.splice(index, 1)
  311. } else {
  312. uni.showToast({ title: '至少保留一个费用项目', icon: 'none' })
  313. }
  314. },
  315. // 上传保养前图片
  316. async uploadBeforeImage() {
  317. try {
  318. const result = await this.$utils.chooseAndUpload()
  319. if (result && result.success) {
  320. console.log(result);
  321. this.stateFrontImage.push(result.url)
  322. }
  323. } catch (error) {
  324. console.error('图片上传失败:', error)
  325. uni.showToast({
  326. title: '图片上传失败',
  327. icon: 'error'
  328. })
  329. }
  330. },
  331. // 删除保养前图片
  332. deleteBeforeImage(index) {
  333. this.stateFrontImage.splice(index, 1)
  334. },
  335. // 上传保养后图片
  336. async uploadAfterImage() {
  337. try {
  338. const result = await this.$utils.chooseAndUpload()
  339. if (result && result.success) {
  340. console.log(result);
  341. this.stateBackImage.push(result.url)
  342. }
  343. } catch (error) {
  344. console.error('头像上传失败:', error)
  345. uni.showToast({
  346. title: '头像上传失败',
  347. icon: 'error'
  348. })
  349. }
  350. },
  351. // 删除保养后图片
  352. deleteAfterImage(index) {
  353. this.stateBackImage.splice(index, 1)
  354. },
  355. // 上传附件
  356. async uploadAttachment() {
  357. try {
  358. const result = await this.$utils.chooseAndUpload()
  359. if (result && result.success) {
  360. console.log(result);
  361. this.remarkImage.push(result.url)
  362. }
  363. } catch (error) {
  364. console.error('头像上传失败:', error)
  365. uni.showToast({
  366. title: '头像上传失败',
  367. icon: 'error'
  368. })
  369. }
  370. },
  371. // 删除附件
  372. deleteAttachment(index) {
  373. this.remarkImage.splice(index, 1)
  374. },
  375. // 预览图片
  376. previewImage(url, imageList) {
  377. uni.previewImage({
  378. urls: imageList,
  379. current: url
  380. })
  381. },
  382. // 提交保养
  383. async submitMaintenance() {
  384. // 表单验证
  385. if (!this.maintenanceName.trim()) {
  386. uni.showToast({ title: '请填写保养人', icon: 'none' })
  387. return
  388. }
  389. if (!this.stateFrontText.trim()) {
  390. uni.showToast({ title: '请填写保养前状态', icon: 'none' })
  391. return
  392. }
  393. if (!this.stateBackText.trim()) {
  394. uni.showToast({ title: '请填写保养后状态', icon: 'none' })
  395. return
  396. }
  397. if (this.isExpend === '1' && !this.amount){
  398. uni.showToast({ title: '请填写消费', icon: 'none' })
  399. return
  400. }
  401. // 把costList切换成字符串并用,,;分割
  402. // 提交数据
  403. const formData = {
  404. maintenanceName: this.maintenanceName,
  405. maintenanceDate: this.maintenanceDate,
  406. stateFrontText: this.stateFrontText,
  407. stateFrontImage: this.stateFrontImage?.join(',') || '',
  408. stateBackText: this.stateBackText,
  409. stateBackImage: this.stateBackImage?.join(',') || '',
  410. expenseList: this.costList.map(item => {
  411. return `${item.name},${item.quantity},${item.amount}`
  412. }).join(';'),
  413. isExpend: this.isExpend,
  414. amount: this.amount,
  415. remarkText: this.remarkText,
  416. remarkImage: this.remarkImage?.join(',') || '',
  417. nextMaintenanceDate: this.nextMaintenanceDate,
  418. remark: this.remark,
  419. id: this.id
  420. }
  421. if (formData.isExpend === '0'){
  422. delete formData.expenseList
  423. }
  424. this.submiting = true
  425. try{
  426. const subRes = await this.$api.exhibit.updateMaintenance(formData)
  427. if(subRes.code == 200){
  428. uni.showToast({ title: subRes.message, icon: 'success' })
  429. // 返回上一页
  430. setTimeout(() => {
  431. uni.navigateBack()
  432. }, 1000)
  433. }else{
  434. uni.showToast({ title: subRes.message, icon: 'none' })
  435. }
  436. }catch(err){
  437. // uni.showToast({ title: err.message, icon: 'none' })
  438. this.submiting = false
  439. }
  440. },
  441. // 获取保养单详情
  442. async queryMaintenanceById() {
  443. try {
  444. const res = await this.$api.exhibit.queryMaintenanceById({
  445. maintenanceId: this.id
  446. })
  447. if (res.code == 200) {
  448. const maintenanceInfo = res.result
  449. // 表单数据
  450. this.maintenanceName = maintenanceInfo.maintenanceName || ''
  451. this.maintenanceDate = maintenanceInfo.maintenanceDate || ''
  452. this.stateFrontText = maintenanceInfo.stateFrontText || ''
  453. // 处理图片字段,去除首尾空格并分割
  454. this.stateFrontImage = maintenanceInfo.stateFrontImage ? maintenanceInfo.stateFrontImage.trim().split(',').filter(img => img.trim()) : []
  455. this.stateBackText = maintenanceInfo.stateBackText || ''
  456. this.stateBackImage = maintenanceInfo.stateBackImage ? maintenanceInfo.stateBackImage.trim().split(',').filter(img => img.trim()) : []
  457. this.isExpend = maintenanceInfo.isExpend || '0'
  458. this.remarkText = maintenanceInfo.remarkText || ''
  459. this.remarkImage = maintenanceInfo.remarkImage ? maintenanceInfo.remarkImage.trim().split(',').filter(img => img.trim()) : []
  460. this.nextMaintenanceDate = maintenanceInfo.nextMaintenanceDate || ''
  461. this.remark = maintenanceInfo.remark || ''
  462. // 处理费用列表数据
  463. if (maintenanceInfo.isExpend === '1' && maintenanceInfo.exhibitMaintenanceExpenses && maintenanceInfo.exhibitMaintenanceExpenses.length > 0){
  464. this.costList = maintenanceInfo.exhibitMaintenanceExpenses.map(item => {
  465. return {
  466. name: item.title || '',
  467. quantity: item.num || '',
  468. amount: item.amount || ''
  469. }
  470. })
  471. } else {
  472. this.costList = [{ name: '', quantity: '', amount: '' }]
  473. }
  474. }
  475. } catch (error) {
  476. uni.showToast({ title: error.message, icon: 'none' })
  477. }
  478. }
  479. },
  480. onLoad(options) {
  481. this.showpieceId = options.showpieceId
  482. this.id = options.id
  483. // 查看保养记录详情
  484. this.queryMaintenanceById()
  485. },
  486. }
  487. </script>
  488. <style lang="scss" scoped>
  489. .maintenance-submit {
  490. min-height: 100vh;
  491. background-color: #f5f5f5;
  492. padding-bottom: 200rpx;
  493. }
  494. .maintenance-info {
  495. margin: 18rpx;
  496. background: #ffffff;
  497. border-radius: 15rpx;
  498. box-shadow: 0rpx 3rpx 6rpx 0rpx rgba(0,0,0,0.16);
  499. padding: 40rpx;
  500. .info-header {
  501. display: flex;
  502. align-items: center;
  503. margin-bottom: 40rpx;
  504. .red-line {
  505. width: 9rpx;
  506. height: 33rpx;
  507. background-color: $primary-color;
  508. margin-right: 7rpx;
  509. border-radius: 5rpx;
  510. }
  511. .info-title {
  512. font-size: 30rpx;
  513. font-weight: bold;
  514. color: $primary-text-color;
  515. }
  516. }
  517. .form-item-header {
  518. border-bottom: none;
  519. margin-top: 20rpx;
  520. }
  521. .form-item {
  522. display: flex;
  523. align-items: center;
  524. justify-content: space-between;
  525. padding: 24rpx 0;
  526. border-bottom: 2rpx solid #f0f0f0;
  527. &:last-child {
  528. border-bottom: none;
  529. }
  530. .label {
  531. font-size: 30rpx;
  532. color: $primary-text-color;
  533. flex-shrink: 0;
  534. &.active {
  535. font-weight: bold;
  536. }
  537. }
  538. .value {
  539. font-size: 30rpx;
  540. color: $secondary-text-color;
  541. &.placeholder {
  542. color: $secondary-text-color;
  543. }
  544. }
  545. .select-area {
  546. display: flex;
  547. align-items: center;
  548. gap: 16rpx;
  549. }
  550. .input-area {
  551. flex: 1;
  552. // background-color: #f5f5f5;
  553. border-radius: 8rpx;
  554. padding: 16rpx 24rpx;
  555. margin-left: 24rpx;
  556. .input-field {
  557. width: 100%;
  558. font-size: 30rpx;
  559. color: $primary-text-color;
  560. background: transparent;
  561. border: none;
  562. outline: none;
  563. text-align: right;
  564. &::placeholder {
  565. color: $secondary-text-color;
  566. }
  567. }
  568. }
  569. .radio-options {
  570. display: flex;
  571. gap: 60rpx;
  572. .radio-item {
  573. display: flex;
  574. align-items: center;
  575. gap: 16rpx;
  576. .radio-dot {
  577. width: 32rpx;
  578. height: 32rpx;
  579. border: 4rpx solid #ddd;
  580. border-radius: 50%;
  581. position: relative;
  582. &.active {
  583. border-color: $primary-color;
  584. &::after {
  585. content: '';
  586. position: absolute;
  587. top: 50%;
  588. left: 50%;
  589. transform: translate(-50%, -50%);
  590. width: 16rpx;
  591. height: 16rpx;
  592. background-color: $primary-color;
  593. border-radius: 50%;
  594. }
  595. }
  596. }
  597. text {
  598. font-size: 30rpx;
  599. color: $secondary-text-color;
  600. &.active {
  601. color: $primary-color;
  602. }
  603. }
  604. }
  605. }
  606. }
  607. .textarea-container {
  608. border-radius: 8rpx;
  609. }
  610. .image-upload {
  611. display: flex;
  612. flex-wrap: wrap;
  613. gap: 24rpx;
  614. margin: 16rpx 0;
  615. .upload-btn {
  616. width: 160rpx;
  617. height: 160rpx;
  618. border: 2rpx dashed $primary-color;
  619. display: flex;
  620. align-items: center;
  621. justify-content: center;
  622. background-color: #fff;
  623. }
  624. .image-item {
  625. position: relative;
  626. width: 160rpx;
  627. height: 160rpx;
  628. image {
  629. width: 100%;
  630. height: 100%;
  631. border-radius: 8rpx;
  632. }
  633. .delete-btn {
  634. position: absolute;
  635. top: -12rpx;
  636. right: -12rpx;
  637. width: 40rpx;
  638. height: 40rpx;
  639. background-color: #ff4757;
  640. border-radius: 50%;
  641. display: flex;
  642. align-items: center;
  643. justify-content: center;
  644. }
  645. }
  646. }
  647. // 费用表格样式
  648. .cost-table {
  649. margin-top: 24rpx;
  650. border: 2rpx solid #f0f0f0;
  651. border-radius: 8rpx;
  652. overflow: hidden;
  653. .table-header {
  654. display: flex;
  655. background-color: #f5f5f5;
  656. padding: 16rpx 0;
  657. .header-cell {
  658. flex: 1;
  659. text-align: center;
  660. font-size: 28rpx;
  661. font-weight: bold;
  662. color: $primary-text-color;
  663. &:first-child {
  664. flex: 2;
  665. }
  666. &:last-child {
  667. width: 120rpx;
  668. flex: none;
  669. }
  670. }
  671. }
  672. .table-row {
  673. display: flex;
  674. border-top: 2rpx solid #f0f0f0;
  675. .cell-input {
  676. flex: 1;
  677. padding: 8rpx;
  678. border-right: 2rpx solid #f0f0f0;
  679. &:first-child {
  680. flex: 2;
  681. }
  682. &:last-child {
  683. border-right: none;
  684. }
  685. }
  686. .cell-action {
  687. width: 120rpx;
  688. display: flex;
  689. align-items: center;
  690. justify-content: center;
  691. gap: 8rpx;
  692. padding: 8rpx;
  693. .action-btn {
  694. width: 36rpx;
  695. height: 36rpx;
  696. padding: 4rpx;
  697. border-radius: 50%;
  698. display: flex;
  699. align-items: center;
  700. justify-content: center;
  701. &.add-btn {
  702. background-color: #1a9c10;
  703. }
  704. &.delete-btn {
  705. background-color: $primary-color;
  706. }
  707. }
  708. }
  709. }
  710. }
  711. }
  712. .submit-container {
  713. position: fixed;
  714. z-index: 999;
  715. bottom: 0;
  716. left: 0;
  717. right: 0;
  718. padding: 32rpx;
  719. background-color: #fff;
  720. border-top: 2rpx solid #f0f0f0;
  721. }
  722. </style>