complaint.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. <template>
  2. <view class="complaint-page">
  3. <template v-if="showComplaintList">
  4. <!-- 处理状态展示 -->
  5. <view class="status-block" v-if="complaintInfo.disposeLogList[0].userType == 1">
  6. <view class="status-title">书嗨处理</view>
  7. <view class="time">--</view>
  8. <view class="status-info">
  9. <view class="info-row">
  10. <text class="label">处理状态:</text>
  11. <text class="value status-text">{{ complaintStatusText }}</text>
  12. </view>
  13. <view class="info-row">
  14. <text class="label">平台回复:</text>
  15. <text class="value">{{ complaintInfo.description || "暂无" }}</text>
  16. </view>
  17. </view>
  18. </view>
  19. <!-- 处理记录时间轴 -->
  20. <view class="complaint-records">
  21. <!-- 我的投诉 -->
  22. <view class="complaint-item" v-for="(item, index) in complaintInfo.disposeLogList" :key="index">
  23. <view class="complaint-header">
  24. <view class="header-main">
  25. <text class="title">{{ item.userType == 1 ? "我的投诉" : "客服回复" }}</text>
  26. <text class="time">{{ item.createTime }}</text>
  27. </view>
  28. </view>
  29. <view class="complaint-content">
  30. <view class="info-row" v-if="item.reason">
  31. <text class="label">投诉原因:</text>
  32. <text class="value">{{ item.reason }}</text>
  33. </view>
  34. <view class="info-row" v-if="item.contactNumber">
  35. <text class="label">联系方式:</text>
  36. <text class="value">{{ item.contactNumber }}</text>
  37. </view>
  38. <view class="info-row">
  39. <text class="label">投诉说明:</text>
  40. <text class="value">{{ item.description }}</text>
  41. </view>
  42. <view class="info-row image-list" v-if="item.imgList && item.imgList.length">
  43. <text class="label">上传凭证:</text>
  44. <view class="images">
  45. <image
  46. v-for="(img, imgIndex) in item.imgList"
  47. :key="imgIndex"
  48. :src="img"
  49. mode="aspectFill"
  50. @click="previewImage(item.imgList, imgIndex)"
  51. >
  52. </image>
  53. </view>
  54. </view>
  55. </view>
  56. </view>
  57. </view>
  58. <view
  59. class="bottom-fixed-con"
  60. v-if="complaintInfo.complaintsStatus == 2 && complaintInfo.disposeLogList[0].userType == 2"
  61. >
  62. <u-button type="primary" @click="continueComplaint">继续投诉</u-button>
  63. </view>
  64. </template>
  65. <!-- 新投诉表单,仅在status为1时显示 -->
  66. <template v-else>
  67. <!-- 表单区域 -->
  68. <view class="form-block">
  69. <!-- 投诉原因 -->
  70. <view class="form-item flex-a">
  71. <view class="common-text-2 required">投诉原因</view>
  72. <view class="input-wrapper flex-1" @click="showReasonPicker">
  73. <text class="placeholder" v-if="!complaintReason">请选择投诉原因</text>
  74. <text v-else>{{ complaintReason }}</text>
  75. <u-icon name="arrow-right" color="#333" size="32" top="3rpx"></u-icon>
  76. </view>
  77. </view>
  78. </view>
  79. <view class="form-block">
  80. <!-- 联系方式 -->
  81. <view class="form-item flex-a" style="padding: 14rpx 0">
  82. <view class="common-text-2 required">联系方式</view>
  83. <u-input
  84. class="flex-1"
  85. input-align="right"
  86. placeholder-style="color:#999;font-size:28rpx;"
  87. v-model="phone"
  88. placeholder="请输入联系方式"
  89. :border="false"
  90. type="number"
  91. maxlength="11"
  92. ></u-input>
  93. </view>
  94. </view>
  95. <view class="common-text-2 required mb-20">投诉说明</view>
  96. <view class="form-block" style="padding: 20rpx">
  97. <!-- 投诉说明 -->
  98. <u-input
  99. v-model="description"
  100. type="textarea"
  101. placeholder="请描述投诉情况,有助于客服更快处理"
  102. :height="200"
  103. :border="false"
  104. ></u-input>
  105. </view>
  106. <!-- 图片上传 -->
  107. <view class="common-text-2 required mb-20">上传凭证(最多3张)</view>
  108. <u-upload
  109. class="upload-image"
  110. :fileList="fileList"
  111. @on-choose-complete="afterRead"
  112. @delete="deletePic"
  113. :maxCount="3"
  114. :auto-upload="false"
  115. :previewFullImage="true"
  116. uploadText="点击上传"
  117. @on-uploaded="onUploaded"
  118. ></u-upload>
  119. <!-- 底部按钮 -->
  120. <view class="bottom-fixed-con">
  121. <u-button type="primary" @click="submitComplaint">提交</u-button>
  122. </view>
  123. <!-- 投诉原因选择器 -->
  124. <u-picker
  125. v-model="showPicker"
  126. mode="selector"
  127. :range="reasonList"
  128. @confirm="confirmReason"
  129. @cancel="showPicker = false"
  130. ></u-picker>
  131. </template>
  132. </view>
  133. </template>
  134. <script>
  135. import ENV_CONFIG from "@/.env.js";
  136. // api前缀
  137. const env = ENV_CONFIG[process.env.ENV_TYPE || "dev"];
  138. export default {
  139. data() {
  140. return {
  141. showComplaintList: false,
  142. complaintReason: "",
  143. phone: "",
  144. description: "",
  145. fileList: [],
  146. showPicker: false,
  147. reasonList: [],
  148. orderId: "",
  149. complaintInfo: {
  150. status: 1,
  151. platformReply: "",
  152. disposeLogList: [],
  153. },
  154. };
  155. },
  156. computed: {
  157. complaintStatusText() {
  158. const status = this.complaintInfo.complaintsStatus;
  159. const statusMap = {
  160. 0: "未投诉过",
  161. 1: "待处理",
  162. 2: "处理中",
  163. 3: "已完结",
  164. };
  165. return statusMap[status] || "未知状态";
  166. },
  167. },
  168. onLoad(ops) {
  169. if (ops.orderId) {
  170. this.orderId = ops.orderId;
  171. this.getComplaintInfo();
  172. }
  173. this.getComplaintsOptions();
  174. },
  175. methods: {
  176. continueComplaint() {
  177. this.showComplaintList = false;
  178. },
  179. // 获取投诉信息
  180. getComplaintInfo() {
  181. uni.$u.http.get(`/token/order/getComplaintsInfo?orderId=${this.orderId}&type=1`).then((res) => {
  182. if (res.code === 200) {
  183. this.complaintInfo = res.data;
  184. this.showComplaintList = res.data.complaintsStatus != 0;
  185. }
  186. });
  187. },
  188. //根据code获取字典 /token/common/getDictOptions
  189. getDict(code) {
  190. return uni.$u.http.get("/token/common/getDictOptions?type=" + code);
  191. },
  192. //获取投诉选项 complaints_options
  193. getComplaintsOptions() {
  194. this.getDict("complaints_options").then((res) => {
  195. if (res.code === 200) {
  196. this.reasonList = res.data.map((item) => item.dictLabel);
  197. }
  198. });
  199. },
  200. showReasonPicker() {
  201. this.showPicker = true;
  202. },
  203. confirmReason(e) {
  204. this.complaintReason = this.reasonList[e[0]];
  205. this.showPicker = false;
  206. },
  207. onUploaded(lists, name) {
  208. console.log(lists, name, "xx111x");
  209. },
  210. afterRead(lists) {
  211. // 先检查token是否存在
  212. const token = uni.getStorageSync("token");
  213. const uploadTasks = lists.map((item) => {
  214. return new Promise((resolve, reject) => {
  215. console.log(item, env.apiUrl + `/api/token/order/complaintUpload/${this.orderId}`, "xx111x");
  216. uni.uploadFile({
  217. url: env.apiUrl + `/api/token/order/complaintUpload/${this.orderId}`,
  218. filePath: item.url,
  219. name: "file",
  220. header: {
  221. Authorization: "Bearer " + token,
  222. },
  223. success: (res) => {
  224. const result = JSON.parse(res.data);
  225. if (result.code === 200 && result.data) {
  226. resolve(result.data);
  227. } else {
  228. uni.$u.toast(result.msg || "上传失败");
  229. reject(new Error(result.msg || "上传失败"));
  230. }
  231. },
  232. fail: (err) => {
  233. uni.$u.toast("上传失败");
  234. reject(err);
  235. },
  236. });
  237. });
  238. });
  239. Promise.all(uploadTasks)
  240. .then((results) => {
  241. this.uploadSuccessList = results.flat();
  242. this.fileList = lists;
  243. console.log(this.fileList, "xx111x", results);
  244. })
  245. .catch((err) => {
  246. console.error("Upload failed:", err);
  247. });
  248. },
  249. deletePic(event) {
  250. this.fileList.splice(event.index, 1);
  251. },
  252. submitComplaint() {
  253. if (!this.complaintReason) {
  254. return uni.$u.toast("请选择投诉原因");
  255. }
  256. if (!this.phone) {
  257. return uni.$u.toast("请输入联系方式");
  258. }
  259. if (!this.description) {
  260. return uni.$u.toast("请输入投诉说明");
  261. }
  262. // 准备投诉数据
  263. const complaintData = {
  264. orderId: this.orderId,
  265. reason: this.complaintReason,
  266. description: this.description,
  267. contactNumber: this.phone,
  268. fileUrls: this.uploadSuccessList,
  269. };
  270. // 提交投诉
  271. uni.$u.http.post("/token/order/addComplaints", complaintData).then((res) => {
  272. if (res.code === 200) {
  273. uni.$u.toast("投诉上报已上报给管理员");
  274. // 返回订单页
  275. setTimeout(() => {
  276. uni.navigateBack({
  277. delta: 1,
  278. });
  279. }, 1500);
  280. } else {
  281. uni.$u.toast(res.msg || "提交失败");
  282. }
  283. });
  284. },
  285. // 图片预览
  286. previewImage(urls, current) {
  287. uni.previewImage({
  288. urls: urls,
  289. current: current,
  290. });
  291. },
  292. },
  293. };
  294. </script>
  295. <style lang="scss">
  296. .complaint-page {
  297. min-height: 100vh;
  298. background: #f8f8f8;
  299. padding: 20rpx;
  300. padding-bottom: 120rpx;
  301. .status-block {
  302. background: #ffffff;
  303. border-radius: 12rpx;
  304. padding: 30rpx;
  305. margin-bottom: 20rpx;
  306. .status-title {
  307. font-size: 32rpx;
  308. font-weight: 600;
  309. color: #222;
  310. }
  311. .divider {
  312. height: 2rpx;
  313. background: #eeeeee;
  314. margin: 20rpx 0;
  315. }
  316. .status-info {
  317. .info-row {
  318. display: flex;
  319. font-size: 28rpx;
  320. line-height: 48rpx;
  321. .label {
  322. color: #333;
  323. min-width: 140rpx;
  324. }
  325. .value {
  326. color: #333;
  327. flex: 1;
  328. }
  329. .status-text {
  330. color: #ff5b5b;
  331. }
  332. }
  333. }
  334. }
  335. .complaint-records {
  336. .complaint-item {
  337. background: #ffffff;
  338. border-radius: 12rpx;
  339. padding: 30rpx;
  340. margin-bottom: 20rpx;
  341. .complaint-header {
  342. margin-bottom: 24rpx;
  343. .header-main {
  344. display: flex;
  345. flex-direction: column;
  346. gap: 8rpx;
  347. .title {
  348. font-size: 32rpx;
  349. font-weight: 600;
  350. color: #222222;
  351. }
  352. .time {
  353. font-size: 26rpx;
  354. color: #999;
  355. }
  356. }
  357. }
  358. .complaint-content {
  359. .info-row {
  360. display: flex;
  361. margin-bottom: 16rpx;
  362. font-size: 28rpx;
  363. line-height: 1.5;
  364. .label {
  365. color: #333;
  366. white-space: nowrap;
  367. }
  368. .value {
  369. color: #333;
  370. flex: 1;
  371. }
  372. }
  373. .image-list {
  374. .label {
  375. display: block;
  376. font-size: 28rpx;
  377. color: #333;
  378. margin-bottom: 16rpx;
  379. }
  380. .images {
  381. display: flex;
  382. flex-wrap: wrap;
  383. gap: 20rpx;
  384. image {
  385. width: 140rpx;
  386. height: 140rpx;
  387. border-radius: 8rpx;
  388. }
  389. }
  390. }
  391. }
  392. }
  393. }
  394. .form-block {
  395. background: #ffffff;
  396. border-radius: 12rpx;
  397. padding: 0 30rpx;
  398. margin-bottom: 20rpx;
  399. }
  400. .required::before {
  401. content: "*";
  402. color: #ff5b5b;
  403. margin-right: 4rpx;
  404. }
  405. .form-item {
  406. padding: 30rpx 0;
  407. .input-wrapper {
  408. display: flex;
  409. justify-content: flex-end;
  410. align-items: center;
  411. font-size: 28rpx;
  412. color: #333;
  413. .placeholder {
  414. color: #999;
  415. }
  416. }
  417. }
  418. }
  419. .upload-image {
  420. .u-list-item {
  421. background: #ffffff !important;
  422. }
  423. }
  424. </style>