complaint.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. <template>
  2. <view class="complaint-root">
  3. <!-- 1. 自定义导航栏 -->
  4. <view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
  5. <view class="nav-content">
  6. <view class="back-area" @click="goBack">
  7. <view class="back-arrow"></view>
  8. </view>
  9. <view class="nav-title">投诉与建议</view>
  10. <view class="right-placeholder"></view>
  11. </view>
  12. </view>
  13. <view class="scroll-container">
  14. <scroll-view scroll-y class="scroll-content" :show-scrollbar="false">
  15. <view class="form-body">
  16. <!-- 2. 类型选择 -->
  17. <view class="section-card">
  18. <view class="section-title">反馈类型</view>
  19. <view class="type-grid">
  20. <view
  21. class="type-item"
  22. v-for="item in types"
  23. :key="item.value"
  24. :class="{active: formData.type === item.value}"
  25. @click="formData.type = item.value">
  26. <text>{{item.label}}</text>
  27. </view>
  28. </view>
  29. </view>
  30. <!-- 3. 问题描述 -->
  31. <view class="section-card">
  32. <view class="section-title">反馈内容</view>
  33. <textarea
  34. class="content-input"
  35. v-model="formData.content"
  36. placeholder="请详细描述您遇到的问题或改进建议..."
  37. maxlength="500"></textarea>
  38. <view class="word-count">{{formData.content.length}}/500</view>
  39. </view>
  40. <!-- 4. 图片上传 -->
  41. <view class="section-card">
  42. <view class="section-title">上传图片 (最多6张)</view>
  43. <view class="upload-grid">
  44. <view class="img-item" v-for="(img, index) in formData.images" :key="index">
  45. <image :src="img" mode="aspectFill" @click="previewImage(index)"></image>
  46. <view class="del-btn" @click.stop="removeImage(index)">
  47. <view class="close-icon">×</view>
  48. </view>
  49. </view>
  50. <view class="add-btn" v-if="formData.images.length < 6" @click="chooseImage">
  51. <text class="add-icon">+</text>
  52. <text class="add-txt">添加图片</text>
  53. </view>
  54. </view>
  55. </view>
  56. </view>
  57. <view class="bottom-placeholder"></view>
  58. </scroll-view>
  59. </view>
  60. <!-- 5. 底部提交按钮 -->
  61. <view class="footer-bar">
  62. <button class="submit-btn" :disabled="!isFormValid" @click="handleSubmit">提交反馈</button>
  63. </view>
  64. </view>
  65. </template>
  66. <script>
  67. export default {
  68. data() {
  69. return {
  70. statusBarHeight: 20,
  71. types: [
  72. { label: '系统投诉', value: 'complaint' },
  73. { label: '改进建议', value: 'suggestion' },
  74. { label: '其他反馈', value: 'other' }
  75. ],
  76. formData: {
  77. type: 'complaint',
  78. content: '',
  79. images: []
  80. }
  81. }
  82. },
  83. computed: {
  84. isFormValid() {
  85. return this.formData.content && this.formData.content.trim().length >= 5;
  86. }
  87. },
  88. onLoad() {
  89. try {
  90. const info = uni.getSystemInfoSync();
  91. this.statusBarHeight = info.statusBarHeight || 20;
  92. } catch (e) {
  93. this.statusBarHeight = 20;
  94. }
  95. },
  96. methods: {
  97. goBack() { uni.navigateBack(); },
  98. chooseImage() {
  99. const count = 6 - this.formData.images.length;
  100. uni.chooseImage({
  101. count: count,
  102. sizeType: ['compressed'],
  103. success: (res) => {
  104. this.formData.images = [...this.formData.images, ...res.tempFilePaths];
  105. }
  106. });
  107. },
  108. removeImage(index) {
  109. this.formData.images.splice(index, 1);
  110. },
  111. previewImage(index) {
  112. uni.previewImage({
  113. urls: this.formData.images,
  114. current: index
  115. });
  116. },
  117. handleSubmit() {
  118. uni.showLoading({ title: '提交中' });
  119. setTimeout(() => {
  120. uni.hideLoading();
  121. uni.showToast({ title: '反馈成功' });
  122. setTimeout(() => {
  123. uni.navigateBack();
  124. }, 1500);
  125. }, 1000);
  126. }
  127. }
  128. }
  129. </script>
  130. <style scoped>
  131. .complaint-root { width: 100vw; height: 100vh; background: #f8fafb; display: flex; flex-direction: column; overflow: hidden; }
  132. .custom-navbar { background: #fff; width: 100%; flex-shrink: 0; border-bottom: 1rpx solid #f0f0f0; }
  133. .nav-content { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 30rpx; }
  134. .back-area { width: 60rpx; height: 44px; display: flex; align-items: center; }
  135. .back-arrow { width: 22rpx; height: 22rpx; border-left: 4rpx solid #333; border-bottom: 4rpx solid #333; transform: rotate(45deg); margin-left: 10rpx; }
  136. .nav-title { font-size: 34rpx; font-weight: bold; color: #333; }
  137. .right-placeholder { width: 60rpx; }
  138. .scroll-container { flex: 1; height: 0; width: 100%; position: relative; }
  139. .scroll-content { width: 100%; height: 100%; }
  140. .form-body { padding: 30rpx; }
  141. .section-card { background: #fff; border-radius: 24rpx; padding: 40rpx 30rpx; margin-bottom: 30rpx; box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.02); }
  142. .section-title { font-size: 30rpx; font-weight: bold; color: #1a1a1a; margin-bottom: 30rpx; border-left: 8rpx solid #C1001C; padding-left: 20rpx; line-height: 1.2; }
  143. .type-grid { display: flex; gap: 20rpx; }
  144. .type-item { flex: 1; height: 80rpx; background: #f5f6f8; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; color: #666; border: 2rpx solid #f5f6f8; transition: all 0.2s; }
  145. .type-item.active { background: rgba(193, 0, 28, 0.05); color: #C1001C; border-color: #C1001C; font-weight: bold; }
  146. .content-input { width: 100%; height: 300rpx; background: #f9fafb; border-radius: 16rpx; padding: 24rpx; box-sizing: border-box; font-size: 30rpx; color: #333; }
  147. .word-count { text-align: right; font-size: 24rpx; color: #ccc; margin-top: 12rpx; }
  148. .upload-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20rpx; }
  149. .img-item { position: relative; width: 100%; padding-top: 100%; }
  150. .img-item image { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: 16rpx; }
  151. .del-btn { position: absolute; top: -10rpx; right: -10rpx; width: 40rpx; height: 40rpx; background: rgba(0,0,0,0.5); border-radius: 50%; display: flex; align-items: center; justify-content: center; z-index: 10; }
  152. .close-icon { color: #fff; font-size: 30rpx; line-height: 1; }
  153. .add-btn { width: 100%; padding-top: 100%; border: 2rpx dashed #ddd; border-radius: 16rpx; display: flex; flex-direction: column; align-items: center; justify-content: center; position: relative; background: #fcfcfc; }
  154. .add-icon { position: absolute; top: 35%; left: 50%; transform: translateX(-50%); font-size: 60rpx; color: #bbb; }
  155. .add-txt { position: absolute; bottom: 20%; left: 50%; transform: translateX(-50%); font-size: 22rpx; color: #999; }
  156. .footer-bar { background: #fff; padding: 30rpx 40rpx calc(30rpx + env(safe-area-inset-bottom)); flex-shrink: 0; border-top: 1rpx solid #f0f0f0; }
  157. .submit-btn { width: 100%; height: 96rpx; background: #C1001C; color: #fff; border-radius: 48rpx; display: flex; align-items: center; justify-content: center; font-size: 32rpx; font-weight: bold; border: none; }
  158. .submit-btn[disabled] { background: #edb3bb !important; color: rgba(255,255,255,0.6) !important; }
  159. .bottom-placeholder { height: 40rpx; }
  160. </style>