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

782 lines
20 KiB

1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months 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. disabled
  19. />
  20. </view>
  21. </view>
  22. <!-- 保养日期 -->
  23. <view class="form-item" @click="showDatePicker">
  24. <text class="label">保养日期</text>
  25. <view class="select-area">
  26. <text class="value" :class="{ placeholder: !maintenanceDate }">{{ maintenanceDate || '请选择' }}</text>
  27. <uv-icon name="arrow-down" size="18" color="#000"></uv-icon>
  28. </view>
  29. </view>
  30. <!-- 保养前状态 -->
  31. <view class="form-item">
  32. <text class="label">保养前状态</text>
  33. </view>
  34. <view class="textarea-container">
  35. <uv-textarea
  36. v-model="stateFrontText"
  37. placeholder="请填写保养前的设备内容"
  38. :maxlength="200"
  39. :show-confirm-bar="false"
  40. height="60"
  41. border="none"
  42. :custom-style="{ backgroundColor: '#f5f5f5' }"
  43. ></uv-textarea>
  44. </view>
  45. <!-- 保养前图片 -->
  46. <view class="image-upload">
  47. <view v-for="(img, index) in stateFrontImage" :key="index" class="image-item">
  48. <image :src="img" mode="aspectFill" @click="previewImage(img, stateFrontImage)"></image>
  49. <view class="delete-btn" @click="deleteBeforeImage(index)">
  50. <uv-icon name="close" size="12" color="#fff"></uv-icon>
  51. </view>
  52. </view>
  53. <view class="upload-btn" @click="uploadBeforeImage">
  54. <uv-icon name="camera" size="34" color="#C70019"></uv-icon>
  55. </view>
  56. </view>
  57. <!-- 保养后状态 -->
  58. <view class="form-item">
  59. <text class="label">保养后状态</text>
  60. </view>
  61. <view class="textarea-container">
  62. <uv-textarea
  63. v-model="stateBackText"
  64. placeholder="请填写保养后的设备内容"
  65. :maxlength="200"
  66. :show-confirm-bar="false"
  67. height="60"
  68. border="none"
  69. :custom-style="{ backgroundColor: '#f5f5f5' }"
  70. ></uv-textarea>
  71. </view>
  72. <!-- 保养后图片 -->
  73. <view class="image-upload">
  74. <view v-for="(img, index) in stateBackImage" :key="index" class="image-item">
  75. <image :src="img" mode="aspectFill" @click="previewImage(img, stateBackImage)"></image>
  76. <view class="delete-btn" @click="deleteAfterImage(index)">
  77. <uv-icon name="close" size="12" color="#fff"></uv-icon>
  78. </view>
  79. </view>
  80. <view class="upload-btn" @click="uploadAfterImage">
  81. <uv-icon name="camera" size="34" color="#C70019"></uv-icon>
  82. </view>
  83. </view>
  84. <!-- 是否产生费用 -->
  85. <view class="form-item">
  86. <text class="label">是否产生费用</text>
  87. <view class="radio-options">
  88. <view
  89. class="radio-item"
  90. :class="{ active: isExpend === '1' }"
  91. @click="selectCost('1')"
  92. >
  93. <view class="radio-dot" :class="{ active: isExpend === '1' }"></view>
  94. <text :class="{ active: isExpend === '1' }"></text>
  95. </view>
  96. <view
  97. class="radio-item"
  98. :class="{ active: isExpend === '0' }"
  99. @click="selectCost('0')"
  100. >
  101. <view class="radio-dot" :class="{ active: isExpend === '0' }"></view>
  102. <text :class="{ active: isExpend === '0' }"></text>
  103. </view>
  104. </view>
  105. </view>
  106. <!-- 产生费用 -->
  107. <view class="form-item" v-if="isExpend === '1'">
  108. <text class="label">产生费用</text>
  109. <view class="input-area" >
  110. <input
  111. v-model="amount"
  112. placeholder="请输入费用"
  113. disabled
  114. class="input-field"
  115. />
  116. </view>
  117. </view>
  118. <!-- 费用详情表格 -->
  119. <view class="cost-table" v-if="isExpend === '1'">
  120. <view class="table-header">
  121. <text class="header-cell">费用名称</text>
  122. <text class="header-cell">数量</text>
  123. <text class="header-cell">金额</text>
  124. <text class="header-cell"></text>
  125. </view>
  126. <view class="table-row" v-for="(item, index) in costList" :key="index">
  127. <view class="cell-input">
  128. <uv-input
  129. v-model="item.name"
  130. placeholder="费用名称"
  131. border="none"
  132. :custom-style="{ backgroundColor: 'transparent', fontSize: '28rpx' }"
  133. ></uv-input>
  134. </view>
  135. <view class="cell-input">
  136. <uv-input
  137. v-model="item.quantity"
  138. placeholder="数量"
  139. border="none"
  140. :custom-style="{ backgroundColor: 'transparent', fontSize: '28rpx' }"
  141. ></uv-input>
  142. </view>
  143. <view class="cell-input">
  144. <uv-input
  145. v-model="item.amount"
  146. placeholder="金额"
  147. type="digit"
  148. border="none"
  149. :custom-style="{ backgroundColor: 'transparent', fontSize: '28rpx' }"
  150. ></uv-input>
  151. </view>
  152. <view class="cell-action">
  153. <view class="action-btn delete-btn" @click="removeCostItem(index)" v-if="costList.length > 1">
  154. <uv-icon name="close" size="14" color="#fff"></uv-icon>
  155. </view>
  156. <view class="action-btn add-btn" @click="addCostItem" v-if="index === costList.length - 1">
  157. <uv-icon name="plus" size="14" color="#fff"></uv-icon>
  158. </view>
  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-datetime-picker
  229. confirm-color="#C70019"
  230. ref="datePicker"
  231. mode="date"
  232. v-model="timeValue"
  233. @confirm="confirmDate"
  234. ></uv-datetime-picker>
  235. <uv-datetime-picker
  236. confirm-color="#C70019"
  237. ref="nextDatePicker"
  238. mode="date"
  239. v-model="nextTimeValue"
  240. @confirm="confirmNextDate"
  241. ></uv-datetime-picker>
  242. </view>
  243. </template>
  244. <script>
  245. export default {
  246. data() {
  247. return {
  248. userId: '',
  249. timeValue: Number(new Date()),
  250. nextTimeValue: Number(new Date()),
  251. // 表单数据
  252. maintenanceName: '',
  253. maintenanceDate: '',
  254. stateFrontText: '',
  255. stateFrontImage: [],
  256. stateBackText: '',
  257. stateBackImage: [],
  258. isExpend: '0',
  259. costList: [{ name: '', quantity: '', amount: '' }],
  260. remarkText: '',
  261. remarkImage: [],
  262. nextMaintenanceDate: '',
  263. remark: '',
  264. showpieceId: '',
  265. submiting: false
  266. }
  267. },
  268. computed: {
  269. // 计算总金额
  270. amount() {
  271. return this.costList.reduce((sum, item) => {
  272. return sum + (Number(item.quantity) * parseFloat(item.amount || 0))
  273. }, 0)
  274. }
  275. },
  276. methods: {
  277. // 获取个人信息
  278. async getPersonalInfo() {
  279. try {
  280. const res = await this.$api.user.queryUser()
  281. if (res.code === 200) {
  282. this.userId = res.result.id
  283. this.maintenanceName = res.result.nickName
  284. }
  285. } catch (error) {
  286. console.error('获取个人信息失败:', error)
  287. }
  288. },
  289. // 显示日期选择器
  290. showDatePicker() {
  291. this.$refs.datePicker.open()
  292. },
  293. // 确认日期
  294. confirmDate(e) {
  295. // uv-datetime-picker返回的是时间戳,需要转换为日期格式
  296. this.maintenanceDate = this.$utils.formatTime(e.value)
  297. },
  298. // 显示下次保养日期选择器
  299. showNextDatePicker() {
  300. this.$refs.nextDatePicker.open()
  301. },
  302. // 确认下次保养日期
  303. confirmNextDate(e) {
  304. // uv-datetime-picker返回的是时间戳,需要转换为日期格式
  305. this.nextMaintenanceDate = this.$utils.formatTime(e.value)
  306. },
  307. // 选择是否产生费用
  308. selectCost(value) {
  309. this.isExpend = value
  310. if (value === '0') {
  311. this.costList = [{ name: '', quantity: '', amount: '' }]
  312. } else if (this.costList.length === 0) {
  313. this.costList = [{ name: '', quantity: '', amount: '' }]
  314. }
  315. },
  316. // 添加费用项目
  317. addCostItem() {
  318. this.costList.push({ name: '', quantity: '', amount: '' })
  319. },
  320. // 删除费用项目
  321. removeCostItem(index) {
  322. if (this.costList.length > 1) {
  323. this.costList.splice(index, 1)
  324. } else {
  325. uni.showToast({ title: '至少保留一个费用项目', icon: 'none' })
  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.userId,
  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. nextMaintenanceDate: this.nextMaintenanceDate,
  431. remark: this.remark,
  432. showpieceId: this.showpieceId
  433. }
  434. if (formData.isExpend === '0'){
  435. delete formData.expenseList
  436. }
  437. this.submiting = true
  438. try{
  439. const subRes = await this.$api.exhibit.addMaintenance(formData)
  440. if(subRes.code == 200){
  441. uni.showToast({ title: subRes.message, icon: 'success' })
  442. // 返回上一页
  443. setTimeout(() => {
  444. uni.navigateBack()
  445. }, 1000)
  446. }else{
  447. uni.showToast({ title: subRes.message, icon: 'none' })
  448. }
  449. }catch(err){
  450. // uni.showToast({ title: err.message, icon: 'none' })
  451. this.submiting = false
  452. }
  453. }
  454. },
  455. async onLoad(options) {
  456. await this.getPersonalInfo()
  457. this.showpieceId = options.id
  458. }
  459. }
  460. </script>
  461. <style lang="scss" scoped>
  462. .maintenance-submit {
  463. min-height: 100vh;
  464. background-color: #f5f5f5;
  465. padding-bottom: 200rpx;
  466. }
  467. .maintenance-info {
  468. margin: 18rpx;
  469. background: #ffffff;
  470. border-radius: 15rpx;
  471. box-shadow: 0rpx 3rpx 6rpx 0rpx rgba(0,0,0,0.16);
  472. padding: 40rpx;
  473. .info-header {
  474. display: flex;
  475. align-items: center;
  476. margin-bottom: 40rpx;
  477. .red-line {
  478. width: 9rpx;
  479. height: 33rpx;
  480. background-color: $primary-color;
  481. margin-right: 7rpx;
  482. border-radius: 5rpx;
  483. }
  484. .info-title {
  485. font-size: 30rpx;
  486. font-weight: bold;
  487. color: $primary-text-color;
  488. }
  489. }
  490. .form-item-header {
  491. border-bottom: none;
  492. margin-top: 20rpx;
  493. }
  494. .form-item {
  495. display: flex;
  496. align-items: center;
  497. justify-content: space-between;
  498. padding: 24rpx 0;
  499. border-bottom: 2rpx solid #f0f0f0;
  500. &:last-child {
  501. border-bottom: none;
  502. }
  503. .label {
  504. font-size: 30rpx;
  505. color: $primary-text-color;
  506. flex-shrink: 0;
  507. &.active {
  508. font-weight: bold;
  509. }
  510. }
  511. .value {
  512. font-size: 30rpx;
  513. color: $secondary-text-color;
  514. &.placeholder {
  515. color: $secondary-text-color;
  516. }
  517. }
  518. .select-area {
  519. display: flex;
  520. align-items: center;
  521. gap: 16rpx;
  522. }
  523. .input-area {
  524. flex: 1;
  525. // background-color: #f5f5f5;
  526. border-radius: 8rpx;
  527. padding: 16rpx 24rpx;
  528. margin-left: 24rpx;
  529. .input-field {
  530. width: 100%;
  531. font-size: 30rpx;
  532. color: $primary-text-color;
  533. background: transparent;
  534. border: none;
  535. outline: none;
  536. text-align: right;
  537. &::placeholder {
  538. color: $secondary-text-color;
  539. }
  540. }
  541. }
  542. .radio-options {
  543. display: flex;
  544. gap: 60rpx;
  545. .radio-item {
  546. display: flex;
  547. align-items: center;
  548. gap: 16rpx;
  549. .radio-dot {
  550. width: 32rpx;
  551. height: 32rpx;
  552. border: 4rpx solid #ddd;
  553. border-radius: 50%;
  554. position: relative;
  555. &.active {
  556. border-color: $primary-color;
  557. &::after {
  558. content: '';
  559. position: absolute;
  560. top: 50%;
  561. left: 50%;
  562. transform: translate(-50%, -50%);
  563. width: 16rpx;
  564. height: 16rpx;
  565. background-color: $primary-color;
  566. border-radius: 50%;
  567. }
  568. }
  569. }
  570. text {
  571. font-size: 30rpx;
  572. color: $secondary-text-color;
  573. &.active {
  574. color: $primary-color;
  575. }
  576. }
  577. }
  578. }
  579. }
  580. .textarea-container {
  581. border-radius: 8rpx;
  582. }
  583. .image-upload {
  584. display: flex;
  585. flex-wrap: wrap;
  586. gap: 24rpx;
  587. margin: 16rpx 0;
  588. .upload-btn {
  589. width: 160rpx;
  590. height: 160rpx;
  591. border: 2rpx dashed $primary-color;
  592. display: flex;
  593. align-items: center;
  594. justify-content: center;
  595. background-color: #fff;
  596. }
  597. .image-item {
  598. position: relative;
  599. width: 160rpx;
  600. height: 160rpx;
  601. image {
  602. width: 100%;
  603. height: 100%;
  604. border-radius: 8rpx;
  605. }
  606. .delete-btn {
  607. position: absolute;
  608. top: -12rpx;
  609. right: -12rpx;
  610. width: 40rpx;
  611. height: 40rpx;
  612. background-color: #ff4757;
  613. border-radius: 50%;
  614. display: flex;
  615. align-items: center;
  616. justify-content: center;
  617. }
  618. }
  619. }
  620. // 费用表格样式
  621. .cost-table {
  622. margin-top: 24rpx;
  623. border: 2rpx solid #f0f0f0;
  624. border-radius: 8rpx;
  625. overflow: hidden;
  626. .table-header {
  627. display: flex;
  628. background-color: #f5f5f5;
  629. padding: 16rpx 0;
  630. .header-cell {
  631. flex: 1;
  632. text-align: center;
  633. font-size: 28rpx;
  634. font-weight: bold;
  635. color: $primary-text-color;
  636. &:first-child {
  637. flex: 2;
  638. }
  639. &:last-child {
  640. width: 120rpx;
  641. flex: none;
  642. }
  643. }
  644. }
  645. .table-row {
  646. display: flex;
  647. border-top: 2rpx solid #f0f0f0;
  648. .cell-input {
  649. flex: 1;
  650. padding: 8rpx;
  651. border-right: 2rpx solid #f0f0f0;
  652. &:first-child {
  653. flex: 2;
  654. }
  655. &:last-child {
  656. border-right: none;
  657. }
  658. }
  659. .cell-action {
  660. width: 120rpx;
  661. display: flex;
  662. align-items: center;
  663. justify-content: center;
  664. gap: 8rpx;
  665. padding: 8rpx;
  666. .action-btn {
  667. width: 36rpx;
  668. height: 36rpx;
  669. padding: 4rpx;
  670. border-radius: 50%;
  671. display: flex;
  672. align-items: center;
  673. justify-content: center;
  674. &.add-btn {
  675. background-color: #1a9c10;
  676. }
  677. &.delete-btn {
  678. background-color: $primary-color;
  679. }
  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>