saoMaBaoMing.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. <template>
  2. <view class="box">
  3. <u-navbar
  4. @leftClick="rightClick"
  5. title="志愿者招募"
  6. bgColor="rgba(255,255,255,0)"
  7. :placeholder="true"
  8. titleStyle="font-weight:bold;color:#000000"
  9. ></u-navbar>
  10. <view class="fj_box">
  11. <view class="scarch_box2">
  12. <u--input
  13. @change="search"
  14. @input="onInputChange"
  15. placeholderStyle="color: #985741"
  16. height="25"
  17. prefixIcon="search"
  18. shape="square"
  19. placeholder="输入文物关键词"
  20. v-model.trim="value"
  21. ></u--input>
  22. </view>
  23. </view>
  24. <view style="position: relative">
  25. <u-transition :show="searchList.length > 0">
  26. <scroll-view v-if="searchList.length > 0" class="search_box" scroll-y="true" @scrolltolower="LoadMore">
  27. <view>
  28. <view @click="searchItem(item)" class="item_tit" v-for="item in searchList" :key="item.id">
  29. {{ item.title }}
  30. </view>
  31. </view>
  32. </scroll-view>
  33. </u-transition>
  34. </view>
  35. <u-popup :show="introduceShow" @close="close" mode="center" :closeable="true" bgColor="#f9dbbf" round="5">
  36. <view class="xx_box">
  37. <view style="font-size: 40rpx; font-weight: bold; text-align: center;">{{ claimDetails.title }}</view>
  38. <view class="xx_tit" v-if="claimDetails.intro">
  39. <u-parse :content="claimDetails.intro"></u-parse>
  40. </view>
  41. <view v-else>
  42. <u-empty text="该文物暂无介绍" iconColor="#4a433d" textColor="#4a433d" mode="data"></u-empty>
  43. </view>
  44. <view style="font-size: 28rpx; color: #666666; padding-top: 10rpx;">{{ claimDetails.address }}</view>
  45. <view class="claim_tit" @click="claimBtn">巡查志愿者报名</view>
  46. </view>
  47. </u-popup>
  48. <view>
  49. <mapComponent
  50. ref="mychild"
  51. @subComponent="subComponent"
  52. :markers="markers"
  53. :height="height"
  54. :latitudeAndLongitude="latitudeAndLongitude"
  55. :introduceShow="introduceShow"
  56. ></mapComponent>
  57. </view>
  58. </view>
  59. </template>
  60. <script>
  61. const ROLE_FIELD_CONFIG = [
  62. {
  63. key: 'patrol',
  64. label: '文物巡查',
  65. fields: ['patrolCount', 'patrol_count', 'wenwu_patrol_count', 'xuncha_count', 'inspection_count']
  66. },
  67. {
  68. key: 'lecture',
  69. label: '宣讲',
  70. fields: ['lectureCount', 'lecture_count', 'xuanjiang_count', 'preach_count']
  71. },
  72. {
  73. key: 'spread',
  74. label: '传播',
  75. fields: ['spreadCount', 'spread_count', 'communication_count', 'propagation_count']
  76. }
  77. ];
  78. // 现在接口里还没有岗位数量,这里先放一组稳定的假数据,方便联调视觉效果。
  79. const ENABLE_MOCK_ROLE_COUNT = true;
  80. const MOCK_ROLE_COUNTS = {
  81. patrol: 2,
  82. lecture: 1,
  83. spread: 3
  84. };
  85. export default {
  86. data() {
  87. return {
  88. volunteer_id: '',
  89. longitude: '',
  90. latitude: '',
  91. page: 1,
  92. searchList: [],
  93. isProcessingItemClick: false,
  94. preventSearchOnChange: false,
  95. prevSearchVal: '',
  96. value: '',
  97. height: '1360',
  98. latitudeAndLongitude: {
  99. latitude: '24.504403',
  100. longitude: '118.143033',
  101. anchorPoint: true
  102. },
  103. markers: [],
  104. claimDetails: {},
  105. introduceShow: false,
  106. entryOptions: {}
  107. };
  108. },
  109. onLoad(options) {
  110. // 扫码进入时,scene 会带在页面参数中,这里统一解析成普通对象。
  111. this.entryOptions = this.parseEntryOptions(options);
  112. this.applyEntryOptions(this.entryOptions);
  113. this.details();
  114. // Temporary: use getScanContentList for scan signup page until the dedicated API is ready.
  115. this.getScanContentList();
  116. },
  117. methods: {
  118. parseEntryOptions(options = {}) {
  119. const parsedOptions = { ...options };
  120. if (options.scene) {
  121. const decodedScene = decodeURIComponent(options.scene);
  122. decodedScene.split('&').forEach((segment) => {
  123. if (!segment) {
  124. return;
  125. }
  126. const [rawKey, rawValue = ''] = segment.split('=');
  127. if (!rawKey) {
  128. return;
  129. }
  130. parsedOptions[rawKey] = decodeURIComponent(rawValue);
  131. });
  132. }
  133. return parsedOptions;
  134. },
  135. // 把二维码参数里的定位或关键词直接带入首屏,方便后面按点位跳转。
  136. applyEntryOptions(options = {}) {
  137. const keyword = options.keywords || options.keyword || options.title || '';
  138. if (keyword) {
  139. this.value = keyword;
  140. this.prevSearchVal = keyword;
  141. }
  142. if (options.latitude && options.longitude) {
  143. this.latitude = options.latitude;
  144. this.longitude = options.longitude;
  145. this.latitudeAndLongitude.latitude = options.latitude;
  146. this.latitudeAndLongitude.longitude = options.longitude;
  147. }
  148. },
  149. search() {
  150. if (this.preventSearchOnChange) {
  151. this.preventSearchOnChange = false;
  152. return;
  153. }
  154. if (this.value) {
  155. if (this.prevSearchVal !== this.value) {
  156. this.prevSearchVal = this.value;
  157. this.page = 1;
  158. this.searchList = [];
  159. }
  160. // Temporary: search suggestions still use getScanContentList and will switch to the new scan API later.
  161. this.$api.getScanContentList(
  162. {
  163. main_body_id: 1,
  164. model_id: 1,
  165. keywords: this.value || '',
  166. page: this.page,
  167. claim_status: '0',
  168. pageSize: '10'
  169. },
  170. (res) => {
  171. const list = Array.isArray(res.data) ? res.data : [];
  172. this.searchList = this.page === 1 ? list : [...this.searchList, ...list];
  173. }
  174. );
  175. } else {
  176. this.searchList = [];
  177. this.page = 1;
  178. this.prevSearchVal = '';
  179. this.getScanContentList();
  180. }
  181. },
  182. onInputChange(value) {
  183. if (!this.isProcessingItemClick) {
  184. this.value = value;
  185. }
  186. },
  187. searchItem(item) {
  188. this.isProcessingItemClick = true;
  189. this.page = 1;
  190. this.value = item.title;
  191. this.prevSearchVal = item.title;
  192. this.searchList = [];
  193. this.$nextTick(() => {
  194. this.isProcessingItemClick = false;
  195. });
  196. this.latitude = item.latitude || '';
  197. this.longitude = item.longitude || '';
  198. this.latitudeAndLongitude.latitude = item.latitude || this.latitudeAndLongitude.latitude;
  199. this.latitudeAndLongitude.longitude = item.longitude || this.latitudeAndLongitude.longitude;
  200. this.getScanContentList();
  201. },
  202. LoadMore() {
  203. if (!this.value) {
  204. return;
  205. }
  206. this.page++;
  207. this.search();
  208. },
  209. getScanContentList() {
  210. // Temporary: use getScanContentList for scan signup page until the dedicated API is ready.
  211. this.$api.getScanContentList(
  212. {
  213. model_id: '1',
  214. main_body_id: '1',
  215. page: 1,
  216. pageSize: '100',
  217. region: 5,
  218. keywords: this.value,
  219. longitude: this.longitude,
  220. latitude: this.latitude
  221. },
  222. (res) => {
  223. const list = Array.isArray(res.data) ? res.data : [];
  224. if (this.value && list.length <= 0) {
  225. this.$common.errorToShow('该文物已被认领');
  226. }
  227. this.markers = list.map((item) => this.buildMarker(item));
  228. }
  229. );
  230. },
  231. // 统一构造 marker,后续换新接口时只需要改这里的字段映射即可。
  232. buildMarker(item) {
  233. return {
  234. id: parseFloat(item.id),
  235. latitude: parseFloat(item.latitude),
  236. longitude: parseFloat(item.longitude),
  237. iconPath: '/static/img/icon_map.png',
  238. width: 1,
  239. height: 1,
  240. alpha: 0,
  241. title: item.title,
  242. customCallout: {
  243. display: 'ALWAYS'
  244. },
  245. joinCluster: true,
  246. };
  247. },
  248. getRoleCount(source, fields = []) {
  249. for (let index = 0; index < fields.length; index++) {
  250. const field = fields[index];
  251. if (source[field] !== undefined && source[field] !== null && source[field] !== '') {
  252. const count = Number(source[field]);
  253. if (!Number.isNaN(count) && count > 0) {
  254. return count;
  255. }
  256. }
  257. }
  258. return 0;
  259. },
  260. // 点击 marker 后先用列表数据秒开岗位标签,再异步拉详情补全图文信息。
  261. subComponent(newShow, id) {
  262. this.introduceShow = newShow;
  263. this.claimDetails = {};
  264. this.$api.getContentDetail(
  265. {
  266. main_body_id: 1,
  267. id: id
  268. },
  269. (res) => {
  270. this.claimDetails = res.data || {};
  271. }
  272. );
  273. },
  274. details() {
  275. this.$api.details({ main_body_id: 1 }, (res) => {
  276. if (res.code == 1) {
  277. this.volunteer_id = res.data.id;
  278. }
  279. });
  280. },
  281. claimBtn() {
  282. if (this.claimDetails.claim_status === 1 && this.claimDetails.is_multiple_claims === 0) {
  283. this.$common.errorToShow('该文物已被认领');
  284. } else if (this.volunteer_id !== undefined && this.volunteer_id !== '') {
  285. this.$api.claimCr(
  286. {
  287. main_body_id: 1,
  288. type: 'volunteer',
  289. volunteer_id: this.volunteer_id,
  290. cr_id: this.claimDetails.id,
  291. cr_code: this.claimDetails.code,
  292. desc: ''
  293. },
  294. (res) => {
  295. this.$common.errorToShow(res.msg);
  296. }
  297. );
  298. } else {
  299. uni.navigateTo({
  300. url: '/index_fenbao/fuWu/baoMing/baoMing2?type=volunteer&id=' + this.claimDetails.id
  301. });
  302. }
  303. },
  304. rightClick() {
  305. uni.navigateBack({
  306. fail() {
  307. uni.switchTab({
  308. url: '/pages/index/index'
  309. });
  310. }
  311. });
  312. },
  313. close() {
  314. this.introduceShow = false;
  315. // 这里不能立刻清空弹窗内容。
  316. // u-popup 的 close 事件发生在收起动画开始时,如果马上把详情数据清掉,
  317. // 用户就会看到同一个弹窗在收起过程中闪成“暂无图片/暂无介绍”的空弹窗。
  318. }
  319. }
  320. };
  321. </script>
  322. <style>
  323. ::v-deep .u-swiper-indicator__wrapper__dot--active {
  324. width: 5px !important;
  325. }
  326. .box {
  327. height: 100%;
  328. width: 100%;
  329. padding-bottom: 50rpx;
  330. background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xy_bgt.png');
  331. background-repeat: repeat-y;
  332. background-size: cover;
  333. background-size: 100% 100%;
  334. }
  335. .fj_box {
  336. width: 90%;
  337. margin: auto;
  338. margin-top: 40rpx;
  339. margin-bottom: 20rpx;
  340. }
  341. .scarch_box2 {
  342. width: 674rpx;
  343. height: 82rpx;
  344. padding: 6rpx 0 0 30rpx;
  345. margin-top: 20rpx;
  346. background-image: url('/static/img/search_bg1.png');
  347. background-size: 100% 100%;
  348. }
  349. .xx_box {
  350. padding: 20rpx;
  351. background-color: #e1bf9a;
  352. width: 660rpx;
  353. padding-top: 80rpx;
  354. }
  355. .xx_tit {
  356. height: 360rpx;
  357. font-size: 30rpx;
  358. padding: 20rpx;
  359. text-indent: 2em;
  360. margin-top: 30rpx;
  361. background-color: #f3e3d3;
  362. overflow: scroll;
  363. }
  364. .role_box {
  365. display: flex;
  366. flex-wrap: wrap;
  367. gap: 18rpx;
  368. margin-top: 24rpx;
  369. }
  370. .role_item {
  371. padding: 12rpx 24rpx;
  372. border-radius: 999rpx;
  373. background: #f7e3d0;
  374. color: #6a412c;
  375. font-size: 28rpx;
  376. line-height: 1;
  377. border: 2rpx solid rgba(202, 86, 66, 0.2);
  378. }
  379. .role_count {
  380. color: #ca5642;
  381. font-weight: 700;
  382. }
  383. .claim_tit {
  384. /* width: 190rpx; */
  385. padding: 8rpx 20rpx;
  386. height: 70rpx;
  387. margin: 30rpx auto 0;
  388. font-weight: 600;
  389. text-align: center;
  390. line-height: 70rpx;
  391. font-size: 32rpx;
  392. background-color: #efb681;
  393. }
  394. .search_box {
  395. width: 87%;
  396. position: absolute;
  397. height: 400rpx;
  398. z-index: 9;
  399. left: 50rpx;
  400. padding: 40rpx;
  401. background-color: #f7dfc0;
  402. }
  403. .item_tit {
  404. line-height: 50rpx;
  405. font-size: 30rpx;
  406. }
  407. </style>