feedback.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  1. <template>
  2. <view class="feedback-container">
  3. <!-- 顶部选项卡 -->
  4. <view class="tab-nav">
  5. <view class="tab-item" :class="{ active: currentTab === 'publish' }" @click="switchTab('publish')">
  6. 发布
  7. </view>
  8. <view class="tab-item" :class="{ active: currentTab === 'my' }" @click="switchTab('my')">
  9. 我的
  10. </view>
  11. </view>
  12. <!-- 发布反馈表单 -->
  13. <view v-if="currentTab === 'publish'" class="content">
  14. <view class="category-selector" @click="openPopup">
  15. <text class="category-text">{{ getCategoryText(feedback.type) || '请选择问题分类' }}</text>
  16. <uni-icons @tap="selectUserPopup" color="#a8aeb4" type="right" size="20"></uni-icons>
  17. </view>
  18. <!-- 文本输入区域 -->
  19. <textarea class="feedback-input" placeholder="如果在使用中遇到问题,请在这里向我们反馈..." v-model="feedback.content"
  20. maxlength="500" @input="handleInput"></textarea>
  21. <view class="word-count">{{feedback.content.length}}/500</view>
  22. <!-- 图片上传区域 -->
  23. <view class="upload-section">
  24. <view class="form-label mar-b-20">上传凭证(可选)</view>
  25. <uni-file-picker @delete="delFile" v-model="imageValue" fileMediatype="image" mode="grid"
  26. @select="select" />
  27. </view>
  28. <!-- 表单区域 -->
  29. <view class="form-section">
  30. <view class="form-item">
  31. <view class="label">
  32. 电话号码(选填)
  33. </view>
  34. <input type="text" placeholder="请填写电话号码" v-model="feedback.phone" />
  35. </view>
  36. <view class="form-item">
  37. <view class="label">邮箱(选填)</view>
  38. <input type="text" placeholder="请填写邮箱" v-model="feedback.email" />
  39. </view>
  40. </view>
  41. </view>
  42. <!-- 我的反馈列表 -->
  43. <view v-else class="my-feedback">
  44. <view class="user-info">
  45. <image class="avatar" :src="feedback.avatar"></image>
  46. <text class="username">{{feedback.account}}</text>
  47. </view>
  48. <view class="feedback-stats">
  49. <view class="stat-item" @click="switchSubTab('feedback')"
  50. :class="{ active: currentSubTab === 'feedback' }">
  51. <text class="stat-value">{{feedback.totalFeedbackCount}}</text>
  52. <text class="stat-label">反馈</text>
  53. </view>
  54. <view class="stat-item" @click="switchSubTab('reply')" :class="{ active: currentSubTab === 'reply' }">
  55. <text class="stat-value">{{feedback.repliedFeedbackCount}}</text>
  56. <text class="stat-label">回复</text>
  57. </view>
  58. </view>
  59. <!-- 反馈列表 -->
  60. <view v-if="currentSubTab === 'feedback'" class="feedback-list">
  61. <view>
  62. <view v-if="list && list.length > 0" class="pad-lr-20 info">
  63. <view v-for="(item, index) in list" :key="index" class="feedback-item">
  64. <view class="feedback-content">
  65. <view class="feedback-type">
  66. <text class="type-tag"
  67. :class="{'type-error': item.type === 1, 'type-suggest': item.type === 2}">
  68. {{ item.type === 1 ? '功能异常' : '产品建议' }}
  69. </text>
  70. </view>
  71. <view class="main-content">{{ item.content }}</view>
  72. <!-- 图片展示区域 -->
  73. <view class="images-container" v-if="item.images">
  74. <image v-for="(img, imgIndex) in item.images.split(',')" :key="imgIndex" :src="img"
  75. mode="aspectFill" class="feedback-image"
  76. @click="previewImage(img, item.images.split(','))"></image>
  77. </view>
  78. <!-- 联系信息 -->
  79. <view class="contact-info" v-if="item.phone || item.email">
  80. <text v-if="item.phone" class="contact-item">电话:{{ item.phone }}</text>
  81. <text v-if="item.email" class="contact-item">邮箱:{{ item.email }}</text>
  82. </view>
  83. <!-- 回复内容 -->
  84. <view class="reply-content" v-if="item.replyContent">
  85. <view class="reply-title">官方回复:</view>
  86. <view class="reply-text">{{ item.replyContent }}</view>
  87. </view>
  88. </view>
  89. </view>
  90. <!-- 加载提示 -->
  91. <view v-if="isLoading" class="loading-tip">加载中...</view>
  92. <!-- 没有更多数据提示 -->
  93. <view v-if="!isLoading && noMoreData" class="no-more-tip">——没有更多数据了——</view>
  94. </view>
  95. <view v-else class="empty-state">
  96. <view class="flex-items-plus">
  97. <image src="../../static/images/empty.png" class="empty"></image>
  98. </view>
  99. <view class="font28 font-gray flex-items-plus">
  100. 暂无反馈数据
  101. </view>
  102. </view>
  103. </view>
  104. </view>
  105. <!-- 回复列表 -->
  106. <view v-else class="reply-list">
  107. <view>
  108. <view v-if="list && list.length > 0" class="pad-lr-20 info">
  109. <view v-for="(item, index) in list" :key="index" class="feedback-item">
  110. <view class="feedback-content">
  111. <view class="feedback-type">
  112. <text class="type-tag"
  113. :class="{'type-error': item.type === 1, 'type-suggest': item.type === 2}">
  114. {{ item.type === 1 ? '功能异常' : '产品建议' }}
  115. </text>
  116. </view>
  117. <view class="main-content">{{ item.content }}</view>
  118. <!-- 图片展示区域 -->
  119. <view class="images-container" v-if="item.images">
  120. <image v-for="(img, imgIndex) in item.images.split(',')" :key="imgIndex" :src="img"
  121. mode="aspectFill" class="feedback-image"
  122. @click="previewImage(img, item.images.split(','))"></image>
  123. </view>
  124. <!-- 联系信息 -->
  125. <view class="contact-info" v-if="item.phone || item.email">
  126. <text v-if="item.phone" class="contact-item">电话:{{ item.phone }}</text>
  127. <text v-if="item.email" class="contact-item">邮箱:{{ item.email }}</text>
  128. </view>
  129. <!-- 回复内容 -->
  130. <view class="reply-content" v-if="item.replyContent">
  131. <view class="reply-title">官方回复:</view>
  132. <view class="reply-text">{{ item.replyContent }}</view>
  133. </view>
  134. </view>
  135. </view>
  136. <!-- 加载提示 -->
  137. <view v-if="isLoading" class="loading-tip">加载中...</view>
  138. <!-- 没有更多数据提示 -->
  139. <view v-if="!isLoading && noMoreData" class="no-more-tip">——没有更多数据了——</view>
  140. </view>
  141. <view v-else class="empty-state">
  142. <view class="flex-items-plus">
  143. <image src="../../static/images/empty.png" class="empty"></image>
  144. </view>
  145. <view class="font28 font-gray flex-items-plus">
  146. 暂无回复数据
  147. </view>
  148. </view>
  149. </view>
  150. </view>
  151. </view>
  152. <!-- 提交按钮 -->
  153. <button class="fixed-button" @click="save" v-if="currentTab === 'publish'">提交反馈</button>
  154. <!-- 分类选择弹窗 -->
  155. <uni-popup ref="popup" type="bottom">
  156. <view class="popup-content">
  157. <view class="popup-header">
  158. <text>问题分类</text>
  159. <text class="close-btn" @click="closePopup">✕</text>
  160. </view>
  161. <view class="category-list">
  162. <view class="category-item" v-for="(item, index) in categories" :key="index"
  163. @click="selectCategory(item)" :class="{'active': getCategoryText(feedback.type) === item}">
  164. {{ item }}
  165. </view>
  166. </view>
  167. </view>
  168. </uni-popup>
  169. <CustomToast ref="customToast" />
  170. </view>
  171. </template>
  172. <script>
  173. import CustomToast from '../../components/CustomToast.vue';
  174. import {
  175. UPLOAD_URL
  176. } from '../../common/config.js';
  177. import {
  178. feedbackSave,
  179. feedbackList,
  180. getFeedback
  181. } from '../../config/api.js';
  182. export default {
  183. components: {
  184. CustomToast,
  185. },
  186. data() {
  187. return {
  188. list: [],
  189. isLoading: false, // 是否正在加载
  190. noMoreData: false, // 是否没有更多数据
  191. imageValue: [],
  192. currentTab: 'publish',
  193. currentSubTab: 'feedback',
  194. token: "",
  195. params: {
  196. current: 1,
  197. type: "",
  198. size: 10,
  199. },
  200. feedback: {
  201. content: "",
  202. type: "",
  203. images: "",
  204. phone: "",
  205. email: "",
  206. imgList: [],
  207. },
  208. categories: ['功能异常或闪退', '产品功能体验与建议']
  209. }
  210. },
  211. onReachBottom() {
  212. if (!this.isLoading && !this.noMoreData) {
  213. this.getList();
  214. }
  215. },
  216. onLoad() {
  217. this.token = uni.getStorageSync('access_token');
  218. this.getDetail()
  219. },
  220. methods: {
  221. getDetail() {
  222. getFeedback().then((res) => {
  223. this.feedback = res.data
  224. })
  225. },
  226. getList() {
  227. uni.showLoading({
  228. title: '加载中...',
  229. mask: true
  230. });
  231. this.isLoading = true;
  232. feedbackList(this.params).then((res) => {
  233. if (res.data.records.length === 0) {
  234. this.noMoreData = true;
  235. } else {
  236. this.list = this.list.concat(res.data.records);
  237. this.params.current++;
  238. }
  239. uni.hideLoading()
  240. this.isLoading = false;
  241. })
  242. },
  243. switchTab(tab) {
  244. this.currentTab = tab;
  245. if (tab === 'my') {
  246. // 切换到"我的"时重置列表相关数据
  247. this.list = [];
  248. this.params.current = 1;
  249. this.noMoreData = false;
  250. this.getList();
  251. }
  252. },
  253. switchSubTab(tab) {
  254. this.currentSubTab = tab;
  255. // 重置列表相关数据
  256. this.list = [];
  257. this.params.current = 1;
  258. this.noMoreData = false;
  259. // 设置type并获取列表
  260. this.params.type = tab === 'reply' ? '1' : '';
  261. this.getList();
  262. },
  263. handleInput(e) {
  264. if (this.feedback.content.length >= 500) {
  265. this.feedback.content = this.feedback.content.slice(0, 500);
  266. }
  267. },
  268. openPopup() {
  269. this.$refs.popup.open();
  270. },
  271. closePopup() {
  272. this.$refs.popup.close();
  273. },
  274. selectCategory(category) {
  275. this.feedback.type = category === '功能异常或闪退' ? '1' : '2';
  276. this.closePopup();
  277. },
  278. getCategoryText(type) {
  279. return type === '1' ? '功能异常或闪退' : type === '2' ? '产品功能体验与建议' : '';
  280. },
  281. // 获取选择的文件
  282. async select(e) {
  283. let list = [].concat(e.tempFiles);
  284. for (let i = 0; i < list.length; i++) {
  285. const result = await this.uploadFilePromise(list[i].url);
  286. this.feedback.imgList.push(result.data.link);
  287. }
  288. },
  289. uploadFilePromise(url) {
  290. return new Promise((resolve, reject) => {
  291. uni.uploadFile({
  292. url: UPLOAD_URL,
  293. filePath: url,
  294. header: {
  295. "Blade-Auth": this.token
  296. },
  297. name: 'file',
  298. formData: {
  299. user: 'test'
  300. },
  301. success: (res) => {
  302. resolve(JSON.parse(res.data));
  303. }
  304. });
  305. });
  306. },
  307. delFile(e) {
  308. this.feedback.imgList.splice(e.index, 1); // 删除对应 index 的附件
  309. },
  310. // 提交反馈
  311. save() {
  312. // 校验问题分类
  313. if (!this.feedback.type) {
  314. uni.showToast({
  315. title: "请选择问题分类",
  316. icon: "none"
  317. })
  318. return;
  319. }
  320. // 校验反馈内容
  321. if (!this.feedback.content.trim()) {
  322. uni.showToast({
  323. title: "请填写反馈内容",
  324. icon: "none"
  325. })
  326. return;
  327. }
  328. this.feedback.images = this.feedback.imgList.join(',');
  329. feedbackSave(this.feedback).then((res) => {
  330. if (res.code == 200) {
  331. this.$refs.customToast.showToast('提交成功');
  332. this.feedback = {
  333. imgList: [],
  334. content: "",
  335. type: "",
  336. images: "",
  337. phone: "",
  338. email: "",
  339. }
  340. this.imageValue = []
  341. }
  342. })
  343. },
  344. // 预览图片
  345. previewImage(current, urls) {
  346. uni.previewImage({
  347. current,
  348. urls
  349. });
  350. },
  351. }
  352. }
  353. </script>
  354. <style lang="scss">
  355. .aite {
  356. height: 50rpx;
  357. width: 50rpx;
  358. margin-right: 20rpx;
  359. margin-left: 10rpx;
  360. }
  361. .category-selector {
  362. background-color: #fff;
  363. padding: 30rpx;
  364. display: flex;
  365. justify-content: space-between;
  366. align-items: center;
  367. margin-bottom: 20rpx;
  368. border-radius: 16rpx;
  369. box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.03);
  370. border: 1rpx solid #f0f0f0;
  371. .category-text {
  372. font-size: 28rpx;
  373. color: #333;
  374. font-weight: 500;
  375. }
  376. .arrow-down {
  377. font-size: 24rpx;
  378. color: #999;
  379. }
  380. }
  381. .popup-content {
  382. background-color: #fff;
  383. border-radius: 24rpx 24rpx 0 0;
  384. padding-bottom: env(safe-area-inset-bottom);
  385. .popup-header {
  386. padding: 32rpx;
  387. display: flex;
  388. justify-content: space-between;
  389. align-items: center;
  390. border-bottom: 1rpx solid #f5f5f5;
  391. text {
  392. font-size: 32rpx;
  393. color: #333;
  394. font-weight: 600;
  395. }
  396. .close-btn {
  397. color: #909399;
  398. font-size: 40rpx;
  399. padding: 10rpx;
  400. }
  401. }
  402. .category-list {
  403. padding: 20rpx 32rpx;
  404. .category-item {
  405. padding: 32rpx 24rpx;
  406. font-size: 28rpx;
  407. color: #333;
  408. border-bottom: 1rpx solid #f5f5f5;
  409. transition: all 0.3s;
  410. &.active {
  411. color: #007AFF;
  412. background-color: #f0f7ff;
  413. border-radius: 8rpx;
  414. }
  415. &:last-child {
  416. border-bottom: none;
  417. }
  418. }
  419. }
  420. }
  421. .fixed-button {
  422. position: fixed;
  423. bottom: 0;
  424. left: 0;
  425. width: 100%;
  426. padding: 15rpx;
  427. background-color: #3C86F7;
  428. color: white;
  429. border: none;
  430. font-size: 32rpx;
  431. font-weight: 600;
  432. }
  433. .feedback-container {
  434. min-height: 100vh;
  435. background-color: #f5f5f5;
  436. padding-bottom: 120rpx;
  437. .nav-bar {
  438. height: 44px;
  439. background-color: #fff;
  440. display: flex;
  441. align-items: center;
  442. padding: 0 15px;
  443. position: relative;
  444. .back-icon {
  445. font-size: 20px;
  446. }
  447. .title {
  448. position: absolute;
  449. left: 50%;
  450. transform: translateX(-50%);
  451. font-size: 16px;
  452. font-weight: 500;
  453. }
  454. }
  455. .tabs {
  456. display: flex;
  457. background-color: #fff;
  458. padding: 10px 15px;
  459. .tab-item {
  460. padding: 8px 20px;
  461. margin-right: 15px;
  462. border-radius: 20px;
  463. font-size: 14px;
  464. &.active {
  465. background-color: #f0f0f0;
  466. }
  467. }
  468. }
  469. .content {
  470. padding: 30rpx;
  471. .feedback-input {
  472. width: 100%;
  473. height: 300rpx;
  474. background-color: #fff;
  475. border-radius: 16rpx;
  476. padding: 24rpx;
  477. font-size: 28rpx;
  478. margin-bottom: 8rpx;
  479. box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.03);
  480. border: 1rpx solid #f0f0f0;
  481. }
  482. .word-count {
  483. text-align: right;
  484. font-size: 24rpx;
  485. color: #909399;
  486. padding: 8rpx 16rpx;
  487. margin-bottom: 20rpx;
  488. }
  489. .upload-section {
  490. background-color: #fff;
  491. border-radius: 16rpx;
  492. padding: 24rpx;
  493. margin-bottom: 20rpx;
  494. box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.03);
  495. border: 1rpx solid #f0f0f0;
  496. .form-label {
  497. font-size: 28rpx;
  498. color: #333;
  499. font-weight: 500;
  500. margin-bottom: 24rpx;
  501. }
  502. }
  503. .form-section {
  504. background-color: #fff;
  505. border-radius: 16rpx;
  506. padding: 0 24rpx;
  507. box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.03);
  508. border: 1rpx solid #f0f0f0;
  509. .form-item {
  510. padding: 24rpx 0;
  511. border-bottom: 1rpx solid #f5f5f5;
  512. &:last-child {
  513. border-bottom: none;
  514. }
  515. .label {
  516. margin-bottom: 16rpx;
  517. font-size: 28rpx;
  518. color: #333;
  519. font-weight: 500;
  520. }
  521. input {
  522. width: 100%;
  523. height: 80rpx;
  524. background-color: #f5f7fa;
  525. border-radius: 8rpx;
  526. padding: 0 24rpx;
  527. font-size: 28rpx;
  528. color: #333;
  529. &::placeholder {
  530. color: #909399;
  531. }
  532. }
  533. }
  534. }
  535. }
  536. .tech-support {
  537. display: flex;
  538. align-items: center;
  539. justify-content: center;
  540. padding: 20px 0;
  541. image {
  542. width: 20px;
  543. height: 20px;
  544. margin-right: 5px;
  545. }
  546. text {
  547. font-size: 12px;
  548. color: #999;
  549. }
  550. }
  551. .tab-bar {
  552. position: fixed;
  553. bottom: 0;
  554. left: 0;
  555. right: 0;
  556. height: 50px;
  557. background-color: #fff;
  558. display: flex;
  559. border-top: 1px solid #f0f0f0;
  560. .tab-bar-item {
  561. flex: 1;
  562. display: flex;
  563. flex-direction: column;
  564. align-items: center;
  565. justify-content: center;
  566. font-size: 12px;
  567. color: #666;
  568. &.active {
  569. color: #007AFF;
  570. }
  571. .iconfont {
  572. font-size: 20px;
  573. margin-bottom: 2px;
  574. }
  575. }
  576. }
  577. .tab-nav {
  578. display: flex;
  579. background-color: #fff;
  580. padding: 20rpx 40rpx;
  581. margin-bottom: 20rpx;
  582. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
  583. position: sticky;
  584. top: 0;
  585. z-index: 100;
  586. .tab-item {
  587. flex: 1;
  588. text-align: center;
  589. font-size: 32rpx;
  590. color: #666;
  591. position: relative;
  592. padding: 20rpx 0;
  593. transition: all 0.3s;
  594. &.active {
  595. color: #007AFF;
  596. font-weight: 600;
  597. &::after {
  598. content: '';
  599. position: absolute;
  600. bottom: 0;
  601. left: 50%;
  602. transform: translateX(-50%);
  603. width: 48rpx;
  604. height: 6rpx;
  605. background-color: #007AFF;
  606. border-radius: 4rpx;
  607. transition: all 0.3s;
  608. }
  609. }
  610. }
  611. }
  612. .my-feedback {
  613. background-color: #fff;
  614. margin: 20rpx;
  615. border-radius: 16rpx;
  616. box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
  617. .user-info {
  618. padding: 40rpx;
  619. text-align: center;
  620. border-bottom: 1rpx solid #f5f5f5;
  621. .avatar {
  622. width: 120rpx;
  623. height: 120rpx;
  624. border-radius: 50%;
  625. margin-bottom: 20rpx;
  626. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
  627. }
  628. .username {
  629. display: block;
  630. font-size: 32rpx;
  631. color: #333;
  632. margin-bottom: 12rpx;
  633. font-weight: 600;
  634. }
  635. .coins {
  636. font-size: 24rpx;
  637. color: #909399;
  638. background-color: #f5f7fa;
  639. padding: 4rpx 16rpx;
  640. border-radius: 20rpx;
  641. display: inline-block;
  642. }
  643. }
  644. .feedback-stats {
  645. display: flex;
  646. padding: 30rpx 0;
  647. border-bottom: 1rpx solid #f5f5f5;
  648. background: linear-gradient(to bottom, #fff, #f9f9f9);
  649. .stat-item {
  650. flex: 1;
  651. text-align: center;
  652. position: relative;
  653. cursor: pointer;
  654. .stat-value {
  655. display: block;
  656. font-size: 36rpx;
  657. color: #333;
  658. margin-bottom: 12rpx;
  659. font-weight: 600;
  660. }
  661. .stat-label {
  662. font-size: 26rpx;
  663. color: #909399;
  664. }
  665. &.active {
  666. .stat-value {
  667. color: #007AFF;
  668. transform: scale(1.05);
  669. transition: all 0.3s;
  670. }
  671. .stat-label {
  672. color: #007AFF;
  673. }
  674. &::after {
  675. content: '';
  676. position: absolute;
  677. bottom: -30rpx;
  678. left: 50%;
  679. transform: translateX(-50%);
  680. width: 48rpx;
  681. height: 6rpx;
  682. background-color: #007AFF;
  683. border-radius: 4rpx;
  684. }
  685. }
  686. }
  687. }
  688. .feedback-list,
  689. .reply-list {
  690. min-height: 300rpx;
  691. }
  692. .no-feedback {
  693. padding: 100rpx 0;
  694. text-align: center;
  695. color: #999;
  696. font-size: 28rpx;
  697. }
  698. .tech-support {
  699. padding: 40rpx;
  700. text-align: center;
  701. image {
  702. width: 40rpx;
  703. height: 40rpx;
  704. vertical-align: middle;
  705. margin-right: 10rpx;
  706. }
  707. text {
  708. font-size: 24rpx;
  709. color: #999;
  710. vertical-align: middle;
  711. }
  712. }
  713. }
  714. }
  715. .loading-tip,
  716. .no-more-tip {
  717. text-align: center;
  718. font-size: 24rpx;
  719. color: #909399;
  720. padding: 30rpx 0;
  721. background: transparent;
  722. }
  723. .pad-lr-20 {
  724. background-color: #f5f5f5;
  725. min-height: 100vh;
  726. padding: 20rpx;
  727. }
  728. .feedback-item {
  729. background-color: #fff;
  730. border-radius: 16rpx;
  731. padding: 30rpx;
  732. margin-bottom: 20rpx;
  733. box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
  734. border: 1rpx solid #f0f0f0;
  735. transition: all 0.3s;
  736. &:hover {
  737. transform: translateY(-2rpx);
  738. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
  739. }
  740. .feedback-type {
  741. margin-bottom: 24rpx;
  742. display: flex;
  743. align-items: center;
  744. justify-content: space-between;
  745. .type-tag {
  746. display: inline-block;
  747. padding: 8rpx 24rpx;
  748. border-radius: 8rpx;
  749. font-size: 24rpx;
  750. font-weight: 500;
  751. &.type-error {
  752. background-color: #FEF0F0;
  753. color: #F56C6C;
  754. border: 1rpx solid rgba(245, 108, 108, 0.2);
  755. }
  756. &.type-suggest {
  757. background-color: #F0F9EB;
  758. color: #67C23A;
  759. border: 1rpx solid rgba(103, 194, 58, 0.2);
  760. }
  761. }
  762. }
  763. .main-content {
  764. font-size: 28rpx;
  765. color: #333;
  766. line-height: 1.6;
  767. margin-bottom: 24rpx;
  768. padding: 0 4rpx;
  769. }
  770. .images-container {
  771. display: flex;
  772. flex-wrap: wrap;
  773. gap: 16rpx;
  774. margin-bottom: 24rpx;
  775. padding: 0 4rpx;
  776. .feedback-image {
  777. width: 180rpx;
  778. height: 180rpx;
  779. border-radius: 12rpx;
  780. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
  781. transition: all 0.3s;
  782. &:active {
  783. transform: scale(0.98);
  784. }
  785. }
  786. }
  787. .contact-info {
  788. font-size: 24rpx;
  789. color: #909399;
  790. margin-bottom: 24rpx;
  791. padding: 16rpx 24rpx;
  792. background-color: #f5f7fa;
  793. border-radius: 12rpx;
  794. .contact-item {
  795. margin-right: 30rpx;
  796. display: inline-flex;
  797. align-items: center;
  798. &:before {
  799. content: '';
  800. display: inline-block;
  801. width: 6rpx;
  802. height: 6rpx;
  803. border-radius: 50%;
  804. background-color: #909399;
  805. margin-right: 8rpx;
  806. }
  807. }
  808. }
  809. .reply-content {
  810. background-color: #f0f7ff;
  811. padding: 24rpx;
  812. border-radius: 12rpx;
  813. margin-top: 16rpx;
  814. border-left: 6rpx solid #007AFF;
  815. .reply-title {
  816. font-size: 26rpx;
  817. color: #333;
  818. margin-bottom: 12rpx;
  819. font-weight: 600;
  820. display: flex;
  821. align-items: center;
  822. &:before {
  823. content: '';
  824. display: inline-block;
  825. width: 6rpx;
  826. height: 6rpx;
  827. border-radius: 50%;
  828. background-color: #007AFF;
  829. margin-right: 8rpx;
  830. }
  831. }
  832. .reply-text {
  833. font-size: 26rpx;
  834. color: #606266;
  835. line-height: 1.6;
  836. padding-left: 16rpx;
  837. }
  838. }
  839. }
  840. .empty-state {
  841. padding: 120rpx 0;
  842. text-align: center;
  843. background: #fff;
  844. margin: 20rpx;
  845. border-radius: 16rpx;
  846. box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
  847. .empty {
  848. width: 240rpx;
  849. height: 240rpx;
  850. margin-bottom: 30rpx;
  851. opacity: 0.8;
  852. }
  853. .font28 {
  854. color: #909399;
  855. font-size: 28rpx;
  856. }
  857. }
  858. </style>