saoMaBaoMing.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  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.volunteer_text">
  39. <u-parse :content="claimDetails.volunteer_text"></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. label: {
  246. content: item.title,
  247. color: '#ffffff',
  248. fontSize: 12,
  249. bgColor: '#e2a0a4',
  250. borderRadius: 20,
  251. padding: 6,
  252. textAlign: 'center',
  253. },
  254. joinCluster: true,
  255. };
  256. },
  257. getRoleCount(source, fields = []) {
  258. for (let index = 0; index < fields.length; index++) {
  259. const field = fields[index];
  260. if (source[field] !== undefined && source[field] !== null && source[field] !== '') {
  261. const count = Number(source[field]);
  262. if (!Number.isNaN(count) && count > 0) {
  263. return count;
  264. }
  265. }
  266. }
  267. return 0;
  268. },
  269. // 点击 marker 后先用列表数据秒开岗位标签,再异步拉详情补全图文信息。
  270. subComponent(newShow, id) {
  271. this.introduceShow = newShow;
  272. this.claimDetails = {};
  273. this.$api.getContentDetail(
  274. {
  275. main_body_id: 1,
  276. id: id
  277. },
  278. (res) => {
  279. this.claimDetails = res.data || {};
  280. }
  281. );
  282. },
  283. details() {
  284. this.$api.details({ main_body_id: 1 }, (res) => {
  285. if (res.code == 1) {
  286. this.volunteer_id = res.data.id;
  287. }
  288. });
  289. },
  290. claimBtn() {
  291. if (this.claimDetails.claim_status === 1 && this.claimDetails.is_multiple_claims === 0) {
  292. this.$common.errorToShow('该文物已被认领');
  293. } else if (this.volunteer_id !== undefined && this.volunteer_id !== '') {
  294. this.$api.claimCr(
  295. {
  296. main_body_id: 1,
  297. type: 'volunteer',
  298. volunteer_id: this.volunteer_id,
  299. cr_id: this.claimDetails.id,
  300. cr_code: this.claimDetails.code,
  301. desc: ''
  302. },
  303. (res) => {
  304. this.$common.errorToShow(res.msg);
  305. }
  306. );
  307. } else {
  308. uni.navigateTo({
  309. url: '/index_fenbao/fuWu/baoMing/baoMing2?type=volunteer&id=' + this.claimDetails.id
  310. });
  311. }
  312. },
  313. rightClick() {
  314. uni.navigateBack({
  315. fail() {
  316. uni.switchTab({
  317. url: '/pages/index/index'
  318. });
  319. }
  320. });
  321. },
  322. close() {
  323. this.introduceShow = false;
  324. // 这里不能立刻清空弹窗内容。
  325. // u-popup 的 close 事件发生在收起动画开始时,如果马上把详情数据清掉,
  326. // 用户就会看到同一个弹窗在收起过程中闪成“暂无图片/暂无介绍”的空弹窗。
  327. }
  328. }
  329. };
  330. </script>
  331. <style>
  332. ::v-deep .u-swiper-indicator__wrapper__dot--active {
  333. width: 5px !important;
  334. }
  335. .box {
  336. height: 100%;
  337. width: 100%;
  338. padding-bottom: 50rpx;
  339. background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xy_bgt.png');
  340. background-repeat: repeat-y;
  341. background-size: cover;
  342. background-size: 100% 100%;
  343. }
  344. .fj_box {
  345. width: 90%;
  346. margin: auto;
  347. margin-top: 40rpx;
  348. margin-bottom: 20rpx;
  349. }
  350. .scarch_box2 {
  351. width: 674rpx;
  352. height: 82rpx;
  353. padding: 6rpx 0 0 30rpx;
  354. margin-top: 20rpx;
  355. background-image: url('/static/img/search_bg1.png');
  356. background-size: 100% 100%;
  357. }
  358. .xx_box {
  359. padding: 20rpx;
  360. background-color: #e1bf9a;
  361. width: 660rpx;
  362. padding-top: 80rpx;
  363. }
  364. .xx_tit {
  365. height: 360rpx;
  366. font-size: 30rpx;
  367. padding: 20rpx;
  368. text-indent: 2em;
  369. margin-top: 30rpx;
  370. background-color: #f3e3d3;
  371. overflow: scroll;
  372. }
  373. .role_box {
  374. display: flex;
  375. flex-wrap: wrap;
  376. gap: 18rpx;
  377. margin-top: 24rpx;
  378. }
  379. .role_item {
  380. padding: 12rpx 24rpx;
  381. border-radius: 999rpx;
  382. background: #f7e3d0;
  383. color: #6a412c;
  384. font-size: 28rpx;
  385. line-height: 1;
  386. border: 2rpx solid rgba(202, 86, 66, 0.2);
  387. }
  388. .role_count {
  389. color: #ca5642;
  390. font-weight: 700;
  391. }
  392. .claim_tit {
  393. /* width: 190rpx; */
  394. padding: 8rpx 20rpx;
  395. margin: 30rpx auto 0;
  396. font-weight: 600;
  397. text-align: center;
  398. line-height: 70rpx;
  399. font-size: 32rpx;
  400. background-color: #efb681;
  401. }
  402. .search_box {
  403. width: 87%;
  404. position: absolute;
  405. height: 400rpx;
  406. z-index: 9;
  407. left: 50rpx;
  408. padding: 40rpx;
  409. background-color: #f7dfc0;
  410. }
  411. .item_tit {
  412. line-height: 50rpx;
  413. font-size: 30rpx;
  414. }
  415. </style>