courseFeedback.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. <template>
  2. <view>
  3. <view class="container" v-if="formInfo&&formInfo.length>0">
  4. <view class="list-item title">投诉原因:{{ text }}</view>
  5. </view>
  6. <view class="formbox" v-if="formInfo&&formInfo.length>0">
  7. <u-form labelPosition="left" labelWidth='80' :model="formdata" :rules="rules" ref="uForm" errorType="toast">
  8. <u-form-item :required="item.isRequire == 1" :label="item.name" :prop="item.desc"
  9. v-for="(item,i) in formInfo" :key="i">
  10. <template v-if="item.type=='文本框'">
  11. <u-input v-model="formdata[item.desc]" border="none" :clearable="true"
  12. :placeholder="'请填写'+item.name"></u-input>
  13. </template>
  14. <template v-if="item.type=='图片'">
  15. <view class="imgitem">
  16. <u-upload :key="(imgdata[item.desc] && imgdata[item.desc].key) || 1" :fileList="(imgdata[item.desc] && Array.isArray(imgdata[item.desc])) ? imgdata[item.desc] : []" @afterRead="afterRead" @delete="deletePic"
  17. :name="item.desc" multiple :maxCount="9">
  18. </u-upload>
  19. </view>
  20. </template>
  21. </u-form-item>
  22. </u-form>
  23. <view class="footer-btn">
  24. <button class="submit-btn" @click="submit">提交</button>
  25. <button class="submit-btn back-btn" @click="goBack">返回</button>
  26. </view>
  27. </view>
  28. <view class="container" v-else>
  29. <view class="list-item title">请选择投诉原因</view>
  30. <view class="list-item" v-for="(item, index) in feedbackItems" :key="item.complaintTypeId || index"
  31. @click="handleClick(item,index)">
  32. <view>{{ item.complaintTypeName }}</view>
  33. <uni-icons type="right" size="20" color="rgba(0,0,0,.3)" v-if="formInfo.length==0 && item.childrenType && item.childrenType.length > 0"></uni-icons>
  34. </view>
  35. <view class="list-item" v-if="pageIndex!=0&&formInfo.length==0" @click="goBack">
  36. 返回上一层
  37. </view>
  38. </view>
  39. </view>
  40. </template>
  41. <script>
  42. import { getTypeTree, complaintRecord } from "@/api/user.js"
  43. import { addReportTalent, addReportVideo } from "@/api/expert.js"
  44. export default {
  45. data() {
  46. return {
  47. statusBarHeight: uni.getSystemInfoSync().statusBarHeight,
  48. menuButtonH: 45,
  49. pageIndex: 0,
  50. list: [],
  51. feedbackItems: [],
  52. userId: '',
  53. courseId: '',
  54. videoId: '',
  55. formInfo: [],
  56. formdata: {},
  57. imgdata: {},
  58. rules: {},
  59. text: '',
  60. templateId: 0,
  61. user: {},
  62. isLastChild: 0,
  63. type:null,
  64. talentId:null,
  65. };
  66. },
  67. onLoad(options) {
  68. if(!this.$isLogin()){
  69. uni.navigateTo({
  70. url: '/pages/auth/loginIndex'
  71. })
  72. return;
  73. }
  74. this.user = this.$getUserInfo()
  75. this.getList();
  76. if(options.courseId){
  77. this.courseId=options.courseId
  78. this.videoId=uni.getStorageSync('videoId')
  79. }
  80. },
  81. onReady() {
  82. //onReady 为uni-app支持的生命周期之一
  83. // this.$refs.uForm.setRules(this.rules)
  84. },
  85. methods: {
  86. goBack() {
  87. // 返回上一层逻辑
  88. if (this.pageIndex == 0) {
  89. uni.navigateBack();
  90. } else {
  91. this.pageIndex--;
  92. this.formInfo = []
  93. this.rules = {}
  94. this.formdata = {}
  95. this.imgdata = {}
  96. if (this.isLastChild == 1) {
  97. this.isLastChild = 0
  98. } else {
  99. if (this.pageIndex == 0) {
  100. this.feedbackItems = this.list
  101. this.templateId = 0
  102. } else {
  103. const list = this.findGrandparentOrAllData(this.list, this.templateId)
  104. this.feedbackItems = list.childrenType || []
  105. this.templateId = list.complaintTypeId || 0
  106. }
  107. }
  108. }
  109. },
  110. findGrandparentOrAllData(data, targetId) {
  111. // 递归函数,用于查找目标节点的父级节点
  112. function findParent(node, targetId) {
  113. if (!node || !node.childrenType || !Array.isArray(node.childrenType)) return null;
  114. for (let child of node.childrenType) {
  115. if (child.complaintTypeId === targetId) {
  116. return node; // 找到目标节点的父级节点
  117. }
  118. const result = findParent(child, targetId); // 递归查找子节点
  119. if (result) return result;
  120. }
  121. return null;
  122. }
  123. // 遍历顶层节点,查找目标节点的父级和祖父级节点
  124. for (let root of data) {
  125. if (root.complaintTypeId === targetId) {
  126. return data; // 如果目标节点是顶层节点,返回所有数据
  127. }
  128. const parent = findParent(root, targetId); // 查找目标节点的父级节点
  129. if (parent) {
  130. const grandparent = findParent(root, parent.complaintTypeId); // 查找父级节点的父级节点
  131. return grandparent || data; // 如果找到祖父节点返回祖父节点,否则返回所有数据
  132. }
  133. }
  134. return data; // 如果没有找到目标节点,返回所有数据
  135. },
  136. handleClick(item, index) {
  137. if (this.isLastChild == 1) return
  138. if (this.pageIndex >= 0) {
  139. this.pageIndex++
  140. const currentItem = this.feedbackItems[index];
  141. let children = currentItem.childrenType || [];
  142. this.templateId = currentItem.complaintTypeId
  143. this.formInfo = []
  144. this.rules = {}
  145. this.formdata = {}
  146. this.imgdata = {}
  147. this.text = currentItem.complaintTypeName
  148. if (children.length > 0) {
  149. // 有子类型,继续显示子类型列表
  150. this.isLastChild = 0
  151. this.feedbackItems = children
  152. }
  153. else {
  154. // 没有子类型,显示表单(最后一级)
  155. this.isLastChild = 1
  156. const rules = {};
  157. const formdata = {};
  158. if (currentItem.description) {
  159. // 如果有description,解析并使用
  160. this.formInfo = JSON.parse(currentItem.description);
  161. } else {
  162. // 如果没有description,使用默认表单(至少有一个内容输入框)
  163. this.formInfo = [{
  164. name: '投诉内容',
  165. desc: 'content',
  166. type: '文本框',
  167. isRequire: 1
  168. },
  169. {
  170. name: '上传图片',
  171. desc: 'urls',
  172. type: '图片',
  173. isRequire: 0
  174. },
  175. ];
  176. }
  177. // 遍历formInfo中的每个对象,初始化表单数据和验证规则
  178. this.formInfo.forEach(descObj => {
  179. const {
  180. isRequire,
  181. desc,
  182. name,
  183. type
  184. } = descObj;
  185. formdata[desc] = ""
  186. if (type == '图片') {
  187. // 确保图片上传组件默认展示,初始化 imgdata
  188. if (!this.imgdata[desc]) {
  189. this.imgdata[desc] = []
  190. }
  191. if (!this.imgdata[desc].key) {
  192. this.imgdata[desc].key = 1
  193. }
  194. }
  195. // 如果isRequire为"1",则添加到rules中
  196. if (isRequire == 1) {
  197. rules[desc] = [{
  198. required: true,
  199. message: name + '不能为空',
  200. trigger: ["change", "blur"]
  201. }];
  202. }
  203. });
  204. this.rules = rules
  205. this.formdata = formdata;
  206. console.log("qxj formdata",this.formdata);
  207. setTimeout(() => {
  208. if (this.$refs.uForm) {
  209. this.$refs.uForm.setRules(this.rules);
  210. }
  211. }, 200)
  212. }
  213. }
  214. },
  215. getList() {
  216. getTypeTree().then(res => {
  217. if (res.code == 200) {
  218. this.list = res.data
  219. this.pageIndex = 0
  220. this.feedbackItems = this.list
  221. } else {
  222. this.list = []
  223. }
  224. })
  225. },
  226. submit() {
  227. const imgs = this.convertArrayToString(this.imgdata)
  228. this.formdata = {
  229. ...this.formdata,
  230. ...imgs
  231. };
  232. this.$refs.uForm.validate().then(res => {
  233. if (res) {
  234. this.doComplaint();
  235. }
  236. })
  237. },
  238. doReportTalent(){ //举报达人
  239. if(!!this.formdata.content){
  240. this.formdata.reportDesc=this.formdata.content;
  241. }
  242. const param = {
  243. talentId: this.talentId,
  244. type: 2,
  245. templateId: this.templateId,
  246. ...this.formdata
  247. }
  248. uni.showLoading({
  249. title:""
  250. });
  251. addReportTalent(param).then(res => {
  252. uni.hideLoading();
  253. if (res.code == 200) {
  254. uni.showModal({
  255. title: '',
  256. content: '我们已收到您的反馈,谢谢',
  257. showCancel: false,
  258. success: (res) => {
  259. this.formInfo = []
  260. this.rules = {}
  261. this.formdata = {}
  262. this.imgdata = {}
  263. this.text = ''
  264. this.templateId = 0
  265. this.isLastChild = 0
  266. this.pageIndex = 0
  267. this.feedbackItems = this.list
  268. }
  269. });
  270. } else {
  271. uni.showToast({
  272. title: res.msg,
  273. icon: 'none'
  274. })
  275. }
  276. },
  277. rej => {
  278. uni.hideLoading();
  279. });
  280. },
  281. doReportVideo(){ //举报短视频
  282. if(!!this.formdata.content){
  283. this.formdata.reportDesc=this.formdata.content;
  284. }
  285. const param = {
  286. videoId: this.videoId,
  287. type: 2,
  288. templateId: this.templateId,
  289. ...this.formdata
  290. }
  291. addReportVideo(param).then(res => {
  292. if (res.code == 200) {
  293. uni.showModal({
  294. title: '',
  295. content: '我们已收到您的反馈,谢谢',
  296. showCancel: false,
  297. success: (res) => {
  298. this.formInfo = []
  299. this.rules = {}
  300. this.formdata = {}
  301. this.imgdata = {}
  302. this.text = ''
  303. this.templateId = 0
  304. this.isLastChild = 0
  305. this.pageIndex = 0
  306. this.feedbackItems = this.list
  307. }
  308. });
  309. } else {
  310. uni.showToast({
  311. title: res.msg,
  312. icon: 'none'
  313. })
  314. }
  315. })
  316. },
  317. doComplaint(){
  318. // 构建符合新接口的参数结构
  319. const param = {
  320. complaintContent: this.formdata.content || "",
  321. complaintTypeId: this.templateId || 0,
  322. complaintUrl: this.formdata.urls || "",
  323. courseId: this.courseId ? parseInt(this.courseId) : 0,
  324. userId: this.user.userId ? parseInt(this.user.userId) : 0,
  325. videoId: this.videoId ? parseInt(this.videoId) : 0
  326. }
  327. complaintRecord(param).then(res => {
  328. if (res.code == 200) {
  329. uni.showModal({
  330. title: '',
  331. content: '我们已收到您的反馈,谢谢',
  332. showCancel: false,
  333. success: (res) => {
  334. this.formInfo = []
  335. this.rules = {}
  336. this.formdata = {}
  337. this.imgdata = {}
  338. this.text = ''
  339. this.templateId = 0
  340. this.isLastChild = 0
  341. this.pageIndex = 0
  342. this.feedbackItems = this.list
  343. }
  344. });
  345. } else {
  346. uni.showToast({
  347. title: res.msg,
  348. icon: 'none'
  349. })
  350. }
  351. })
  352. },
  353. convertArrayToString(obj) {
  354. const result = {};
  355. for (const key in obj) {
  356. if (obj.hasOwnProperty(key)) {
  357. const value = obj[key];
  358. // 如果值是数组,则将其转换为逗号分隔的字符串
  359. if (Array.isArray(value)) {
  360. result[key] = value.filter(item => item.status == 'success').map(it => it.url).join(
  361. ","); // 使用逗号分隔数组元素
  362. } else {
  363. result[key] = value; // 保持原样
  364. }
  365. }
  366. }
  367. return result;
  368. },
  369. deletePic(event) {
  370. const fieldName = event.name;
  371. const index = event.index;
  372. if (this.imgdata[fieldName] && Array.isArray(this.imgdata[fieldName])) {
  373. this.imgdata[fieldName].splice(index, 1);
  374. }
  375. this.$forceUpdate()
  376. this.imgdata[fieldName].key --
  377. },
  378. async afterRead(event) {
  379. const fieldName = event.name;
  380. if (!this.imgdata[fieldName]) {
  381. this.imgdata[fieldName] = [];
  382. this.imgdata[fieldName].key = 1
  383. }
  384. let lists = Array.isArray(event.file) ? event.file : [event.file];
  385. for (let i = 0; i < lists.length; i++) {
  386. this.imgdata[fieldName].push({
  387. ...lists[i],
  388. status: 'uploading',
  389. message: '上传中...'
  390. });
  391. this.$forceUpdate()
  392. this.imgdata[fieldName].key ++
  393. // 调用上传方法
  394. const result = await this.uploadFilePromise(lists[i].url);
  395. if (result.code == 200) {
  396. this.imgdata[fieldName][this.imgdata[fieldName].length - 1] = {
  397. ...this.imgdata[fieldName][this.imgdata[fieldName].length - 1],
  398. url: result.url,
  399. status: 'success',
  400. message: ''
  401. };
  402. this.$forceUpdate()
  403. this.imgdata[fieldName].key ++
  404. } else {
  405. this.imgdata[fieldName][this.imgdata[fieldName].length - 1] = {
  406. ...this.imgdata[fieldName][this.imgdata[fieldName].length - 1],
  407. status: 'fail',
  408. message: '上传失败'
  409. };
  410. this.$forceUpdate()
  411. this.imgdata[fieldName].key ++
  412. uni.showToast({
  413. title: '上传失败',
  414. icon: 'error'
  415. });
  416. }
  417. }
  418. },
  419. uploadFilePromise(url) {
  420. return new Promise((resolve, reject) => {
  421. let a = uni.uploadFile({
  422. url: uni.getStorageSync('requestPath') + '/app/common/uploadOSS', // 仅为示例,非真实的接口地址
  423. filePath: url,
  424. name: 'file',
  425. formData: {
  426. user: 'test'
  427. },
  428. success: (res) => {
  429. resolve(JSON.parse(res.data))
  430. },
  431. fail: (err) => {
  432. uni.showToast({
  433. title: '上传失败',
  434. icon: 'error'
  435. })
  436. }
  437. });
  438. })
  439. }
  440. }
  441. };
  442. </script>
  443. <style scoped lang="scss">
  444. .formbox {
  445. background-color: #fff;
  446. padding: 0 30rpx;
  447. }
  448. .footer-btn {
  449. width: 100%;
  450. box-sizing: border-box;
  451. padding: 32rpx;
  452. display: flex;
  453. flex-direction: column;
  454. align-items: center;
  455. }
  456. .back-btn {
  457. margin-top: 40rpx;
  458. color: #FF5C03 !important;
  459. background: #fff !important;
  460. border: 1rpx solid #FF5C03;
  461. }
  462. .submit-btn {
  463. width: 100%;
  464. height: 88rpx;
  465. line-height: 88rpx;
  466. text-align: center;
  467. font-size: 30rpx;
  468. font-family: PingFang SC;
  469. font-weight: bold;
  470. color: #FFFFFF;
  471. background: #FF5C03;
  472. border-radius: 44rpx;
  473. border: 1rpx solid #FF5C03;
  474. &::after {
  475. border: none;
  476. }
  477. }
  478. .arrow-left {
  479. position: absolute;
  480. left: 24rpx;
  481. height: 88rpx;
  482. display: flex;
  483. align-items: center;
  484. justify-content: center;
  485. overflow: hidden;
  486. }
  487. .list-item {
  488. background-color: #fff;
  489. padding: 24rpx;
  490. border-bottom: 1rpx solid #f4f4f4;
  491. font-size: 15px;
  492. color: #333;
  493. display: flex;
  494. align-items: center;
  495. justify-content: space-between;
  496. }
  497. .title {
  498. color: rgba(0, 0, 0, .5);
  499. background-color: transparent;
  500. border-top: 1rpx solid #f4f4f4;
  501. }
  502. .imgitem {
  503. display: flex;
  504. align-items: center;
  505. justify-content: flex-start;
  506. .icon {
  507. min-width: 30rpx;
  508. margin-right: 15rpx;
  509. width: 30rpx;
  510. height: 30rpx;
  511. }
  512. }
  513. </style>