TaskDetail.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. <template>
  2. <div class="task-box">
  3. <!-- 分享的图片展示 -->
  4. <el-image v-if="showImg" style="width: 100px; height: 100px" :src="taskInfo.shareImgUrl" :preview-src-list="[taskInfo.shareImgUrl]"> </el-image>
  5. <template v-if="configList && configList.length > 0">
  6. <div style="max-height: 270px; overflow: auto">
  7. <div v-for="item in configList" :key="item.id" class="field-box">
  8. <template v-if="item.taskFiledType == 'domain' || item.taskFiledType == 'select' || item.taskFiledType == 'tree'">
  9. <div class="title-value">{{ item.taskFiledValue }}:</div>
  10. <div class="desc-value" v-show="!(item.taskTypeId == '19' && item.taskFiledKey == 'temp3')">
  11. {{ getDesc(item) }}
  12. </div>
  13. </template>
  14. <!-- 城市选择 -->
  15. <template v-if="item.taskFiledType === 'area'">
  16. <div class="title-value">{{ item.taskFiledValue }}:</div>
  17. <div class="desc-value">
  18. {{ getAddress(item) }}
  19. </div>
  20. </template>
  21. <!-- 文字输入 -->
  22. <template v-if="item.taskFiledType == 'text' || item.taskFiledType == 'inputautoselect' || item.taskFiledType == 'map' || item.taskFiledType == 'mapwithimg'">
  23. <div class="title-value">{{ item.taskFiledValue }}:</div>
  24. <div class="desc-value">
  25. {{ wmTaskContent[item.taskFiledKey] || '--' }}
  26. </div>
  27. <!-- 用temp17存储打卡图片 -->
  28. <div v-if="item.taskFiledType == 'mapwithimg' && !!wmTaskContent['temp17']" class="location-img">
  29. <div class="field-text">位置打卡图片</div>
  30. <div class="img-box">
  31. <el-image class="img-item" :src="baseUrl + wmTaskContent['temp17']" :preview-src-list="getPreviewList([baseUrl + wmTaskContent['temp17']], 0)"> </el-image>
  32. </div>
  33. </div>
  34. </template>
  35. <!-- 时间 -->
  36. <template v-if="item.taskFiledType == 'datetime'">
  37. <div class="title-value">{{ item.taskFiledValue }}:</div>
  38. <div class="desc-value">
  39. {{ wmTaskContent[item.taskFiledKey] || '--' }}
  40. </div>
  41. </template>
  42. <!-- 日期范围 -->
  43. <template v-if="item.taskFiledType == 'datatimerange'">
  44. <div class="title-value">{{ item.taskFiledValue }}:</div>
  45. <div class="desc-value">
  46. {{ wmTaskContent[item.taskFiledKey] }}
  47. </div>
  48. </template>
  49. <!-- 签名 -->
  50. <div v-if="item.taskFiledType == 'sign'" class="divline" style="width: 100%; height: 15px; background-color: #e9e9e9; margin-bottom: 5px"></div>
  51. <template v-if="item.taskFiledType == 'sign'">
  52. <div class="upload-field">
  53. <div class="field-text" :class="{ required: item.isMustfill == '1' }">{{ item.taskFiledValue }}:</div>
  54. <!-- 上传组件 -->
  55. <div class="img-box">
  56. <el-image
  57. :key="index"
  58. class="img-item"
  59. :src="baseUrl + wmTaskContent[item.taskFiledKey]"
  60. :preview-src-list="getPreviewList([baseUrl + wmTaskContent[item.taskFiledKey]], 0)"
  61. >
  62. </el-image>
  63. </div>
  64. </div>
  65. </template>
  66. <!-- 图片上传 -->
  67. <div v-if="item.taskFiledType == 'img'" class="divline" style="width: 100%; height: 15px; background-color: #e9e9e9; margin-bottom: 5px"></div>
  68. <template v-if="item.taskFiledType == 'img'">
  69. <div class="upload-field">
  70. <div class="field-text" :class="{ required: item.isMustfill == '1' }">
  71. {{ item.taskFiledValue }}:
  72. <!-- {{ getImgList(wmTaskContent[item.taskFiledKey], item) }} -->
  73. </div>
  74. <!-- 上传组件 -->
  75. <div class="img-box" v-if="item.imgList">
  76. <div v-for="(iItem, index) in item.imgList" :key="index" class="img-box-content">
  77. <span class="type">{{ iItem.type }}</span>
  78. <el-image class="img-item" lazy :src="iItem.url" :preview-src-list="getPreviewList(item.previewList, index)" />
  79. </div>
  80. </div>
  81. </div>
  82. </template>
  83. <!-- 文件类型 -->
  84. <template v-if="item.taskFiledType === 'fileurl'">
  85. <div class="upload-field">
  86. <div class="field-text" :class="{ required: item.isMustfill == '1' }">
  87. {{ item.taskFiledValue }}:
  88. {{ getFileList(wmTaskContent[item.taskFiledKey], item) }}
  89. </div>
  90. <div v-if="item.fileList" class="pl60 pr60">
  91. <!-- 图片文件 -->
  92. <div class="img-box">
  93. <template v-for="(iItem, index) in item.fileList">
  94. <el-image
  95. v-if="imgTypeList.includes(iItem.url.split('.')[1])"
  96. :key="index"
  97. class="img-item"
  98. :src="iItem.url"
  99. :preview-src-list="
  100. getPreviewList(
  101. item.fileList.map((ele) => ele.url),
  102. index
  103. )
  104. "
  105. >
  106. </el-image>
  107. </template>
  108. </div>
  109. <!-- 其他文件 -->
  110. <div class="file-box">
  111. <template v-for="(iItem, index) in item.fileList">
  112. <div v-if="!imgTypeList.includes(iItem.url.split('.')[1])" :key="index" class="df_sb file-item">
  113. <span>{{ iItem.fileName }}</span>
  114. <span @click="downloadFn(iItem)">下载</span>
  115. </div>
  116. </template>
  117. </div>
  118. </div>
  119. </div>
  120. </template>
  121. <!-- 金额 -->
  122. <template v-if="item.taskFiledType == 'money'">
  123. <div class="title-value">{{ item.taskFiledValue }}:</div>
  124. <div class="desc-value">
  125. {{ wmTaskContent[item.taskFiledKey] || '--' }}
  126. </div>
  127. </template>
  128. <!-- 数字 -->
  129. <template v-if="item.taskFiledType == 'number'">
  130. <div class="title-value">{{ item.taskFiledValue }}:</div>
  131. <div class="desc-value">
  132. {{ wmTaskContent[item.taskFiledKey] || '--' }}
  133. </div>
  134. </template>
  135. <!-- 长文本 -->
  136. <template v-if="item.taskFiledType == 'longtext'">
  137. <div class="longtext-box">
  138. <div class="field-text" :class="{ required: item.isMustfill == '1' }">{{ item.taskFiledValue }}:</div>
  139. <textarea class="textarea-box" :disabled="true" :value="wmTaskContent[item.taskFiledKey] || '--'" :maxlength="item.taskFiledMaxsize" />
  140. </div>
  141. </template>
  142. </div>
  143. </div>
  144. </template>
  145. </div>
  146. </template>
  147. <script>
  148. import taskApi from '@/api/taskDetail/task';
  149. import commonApi from '@/api/taskDetail/common';
  150. import provinces from '@/util/lib/province';
  151. import citys from '@/util/lib/city';
  152. import areas from '@/util/lib/area';
  153. import request from '@/router/axios';
  154. export default {
  155. name: 'index',
  156. data() {
  157. return {
  158. baseUrl: process.env.VUE_APP_URL,
  159. showImg: false, // 是否显示只展示图片,分享用到
  160. taskInfo: {},
  161. wmTaskContent: {},
  162. taskTypeId: 0, // 任务类型id
  163. configList: [], // 配置列表
  164. dictList: [], //字典
  165. imgTypeList: ['jpeg', 'jpg', 'png', 'git', 'bmp'] // 能查看的文件类型
  166. };
  167. },
  168. props: {
  169. id: {
  170. type: String
  171. }
  172. },
  173. methods: {
  174. // 获取地址
  175. getAddress(item) {
  176. if (item.taskFiledType == 'area') {
  177. let e = this.wmTaskContent[item.taskFiledKey];
  178. if (!e) return '--';
  179. let arr = e.split(',');
  180. let provincesName = '';
  181. let provincesIndex = '';
  182. let cityName = '';
  183. let cityIndex = '';
  184. let areasName = '';
  185. // 如果为数字,方形,不为数组则展示
  186. var n = parseInt(arr[0]);
  187. // 如果不为数字,则返回
  188. if (isNaN(n)) {
  189. return arr[0];
  190. }
  191. if (arr[0]) {
  192. provinces.map((v, k) => {
  193. if (v.value + '0000' == arr[0]) {
  194. provincesName = v.label;
  195. provincesIndex = k;
  196. }
  197. });
  198. }
  199. if (arr[1]) {
  200. citys[provincesIndex].map((v, k) => {
  201. if (v.value + '00' == arr[1]) {
  202. cityName = v.label;
  203. cityIndex = k;
  204. }
  205. });
  206. }
  207. if (arr[2]) {
  208. areas[provincesIndex][cityIndex].map((v, k) => {
  209. if (v.value == arr[2]) {
  210. areasName = v.label;
  211. }
  212. });
  213. }
  214. return `${provincesName}-${cityName}-${areasName}`;
  215. }
  216. },
  217. // 获取详情
  218. getDesc(item) {
  219. // 拜访类任务
  220. const arr = ['51', '52', '53'];
  221. if (arr.includes(item.taskTypeId) && item.taskFiledKey === 'temp24') {
  222. return this.wmTaskContent['temp24label'];
  223. }
  224. // 获取需要转换的字符串(例如 '1,2,3')
  225. const contentValue = this.wmTaskContent[item.taskFiledKey];
  226. if (!contentValue) return '--';
  227. // 获取字典表对应数组
  228. const itemList = this.dictList[item.dictGroupName] || [];
  229. // 按逗号拆分成数组
  230. const valueArray = contentValue.split(',');
  231. // 将每个拆分值找到对应的 label
  232. const result = valueArray.map((val) => {
  233. const foundItem = itemList.find((dictItem) => dictItem.value === val);
  234. return foundItem ? foundItem.label : null;
  235. });
  236. // 如果有任意一个值无法找到对应的 label,则直接返回 '--'
  237. if (result.some((label) => label === null)) {
  238. return '--';
  239. }
  240. // 否则,使用逗号拼接并返回
  241. return result.join(',');
  242. },
  243. // 得出图片
  244. // 得出图片
  245. getImgList(urlStr, item) {
  246. // 如果字段类型不是图片或没有 urlStr,则直接返回空数组
  247. if (item.taskFiledType !== 'img' || !urlStr) return [[], []];
  248. // 将字符串按逗号拆分为数组
  249. const urlArr = urlStr.split(',');
  250. // 如果拆分后为空,也返回空数组
  251. if (!urlArr.length) return [[], []];
  252. // 使用 reduce 一次性构建 imgList 和 previewList
  253. const [imgList, previewList] = urlArr.reduce(
  254. (acc, curUrl) => {
  255. let [imgListAcc, previewListAcc] = acc;
  256. let type = '无';
  257. let newUrl = curUrl;
  258. if (curUrl.includes(';1')) {
  259. type = '拍照';
  260. newUrl = curUrl.replace(';1', '');
  261. } else if (curUrl.includes(';2')) {
  262. type = '相册';
  263. newUrl = curUrl.replace(';2', '');
  264. }
  265. const fullUrl = process.env.VUE_APP_URL + newUrl;
  266. imgListAcc.push({ type, url: fullUrl });
  267. previewListAcc.push(fullUrl);
  268. return [imgListAcc, previewListAcc];
  269. },
  270. [[], []]
  271. );
  272. return [imgList, previewList];
  273. },
  274. // 得出文件 jsonStr: temp字符串
  275. getFileList(jsonStr, item) {
  276. if (item.taskFiledType !== 'fileurl') return '';
  277. if (!jsonStr) return '';
  278. const parseFileList = JSON.parse(jsonStr);
  279. if (!parseFileList) return '';
  280. let fileList = [];
  281. parseFileList.forEach((fileItem) => {
  282. fileList.push({
  283. fileName: fileItem.fileName,
  284. url: process.env.VUE_APP_URL + fileItem.url
  285. });
  286. });
  287. item.fileList = parseFileList;
  288. return '';
  289. },
  290. // 下载文件
  291. downloadFn(item) {
  292. request({
  293. url: item.url,
  294. method: 'get',
  295. responseType: 'blob'
  296. }).then((response) => {
  297. // 处理返回的文件流
  298. const blob = response.data;
  299. const link = document.createElement('a');
  300. link.href = URL.createObjectURL(blob);
  301. link.download = item.fileName;
  302. document.body.appendChild(link);
  303. link.click();
  304. window.setTimeout(function () {
  305. URL.revokeObjectURL(blob);
  306. document.body.removeChild(link);
  307. }, 0);
  308. });
  309. },
  310. // 获取任务详情
  311. async getTaskInfoById(id) {
  312. try {
  313. const res = await taskApi.getTaskInfoById(id);
  314. const { data } = res.data;
  315. this.wmTaskContent = data.wmTaskContent;
  316. this.taskInfo = data.taskInfo;
  317. this.taskTypeId = data.taskType;
  318. this.getTaskContentConfigByTaskTypeId(data.taskType);
  319. } catch (e) {
  320. console.log(e);
  321. }
  322. },
  323. // 获取配置
  324. async getTaskContentConfigByTaskTypeId(value) {
  325. try {
  326. let res = await commonApi.getTaskContentConfigByTaskTypeId(value);
  327. let { config, dict } = res.data.data;
  328. this.configList = config;
  329. this.dictList = dict;
  330. this.configList.forEach((item) => {
  331. item.taskFiledType = item.taskFiledType.trim();
  332. if (item.taskFiledType == 'img') {
  333. let imgArr = this.getImgList(this.wmTaskContent[item.taskFiledKey], item);
  334. item.imgList = imgArr[0];
  335. item.previewList = imgArr[1];
  336. }
  337. });
  338. } catch (e) {
  339. console.log(e);
  340. }
  341. this.getMapAddress();
  342. // uni.hideLoading();
  343. },
  344. getPreviewList(imgList, index) {
  345. // 解决el-image大图预览定位图片不准确的问题
  346. return imgList.slice(index).concat(imgList.slice(0, index));
  347. }
  348. },
  349. mounted() {
  350. this.getTaskInfoById(this.id);
  351. }
  352. };
  353. </script>
  354. <style lang="scss" scoped>
  355. .field-box {
  356. //display: flex;
  357. padding: 0 20px 10px;
  358. overflow: hidden;
  359. .title-value {
  360. //width: 50%;
  361. float: left;
  362. }
  363. .desc-value {
  364. float: left;
  365. }
  366. .upload-field {
  367. .img-box {
  368. padding-top: 10px;
  369. display: flex;
  370. flex-wrap: wrap;
  371. }
  372. .file-box {
  373. .file-item {
  374. padding: 2px 8px;
  375. cursor: pointer;
  376. &:hover {
  377. background-color: #f5f7fa;
  378. color: #2d8cf0;
  379. }
  380. }
  381. }
  382. }
  383. .img-box-content {
  384. padding-top: 15px;
  385. position: relative;
  386. .type {
  387. font-size: 12px;
  388. position: absolute;
  389. top: -3px;
  390. }
  391. }
  392. .img-item {
  393. width: 100px;
  394. height: 100px;
  395. margin-right: 10px;
  396. }
  397. .longtext-box {
  398. width: 50%;
  399. .textarea-box {
  400. margin-top: 10px !important;
  401. width: 100%;
  402. }
  403. }
  404. }
  405. .sign-in-box {
  406. padding: 10px 20px;
  407. }
  408. .detail-info {
  409. ul {
  410. list-style: none;
  411. li {
  412. span {
  413. min-width: 80px;
  414. }
  415. display: flex;
  416. margin-bottom: 5px;
  417. }
  418. }
  419. }
  420. </style>