You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

499 lines
11 KiB

8 months ago
8 months ago
  1. <template>
  2. <view class="home">
  3. <view class="home-top">
  4. <view class="search">
  5. <view @click="showSelectArea" class="left-area">
  6. <image src="@/static/home/address-icon.png"></image>
  7. <view class="area">{{ area }}</view>
  8. <image src="../../static/home/arrow-icon.png" mode="aspectFit"></image>
  9. <view class="parting-line">|</view>
  10. </view>
  11. <view class="center-area">
  12. <image src="@/static/home/search-icon.png"></image>
  13. <van-field @click="searchAddress" v-model="queryParams.title" center placeholder="请选择地区" />
  14. </view>
  15. <view class="right-area">
  16. <view @click="searchAddress" class="search-button">
  17. 搜索
  18. </view>
  19. </view>
  20. </view>
  21. </view>
  22. <view class="home-content">
  23. <view class="banner b-relative">
  24. <van-swipe class="my-swipe" :autoplay="3000" indicator-color="white">
  25. <van-swipe-item v-for="item in bannerList" :key="item.id">
  26. <image class="banner-image" :src="item.image" mode="widthFix"></image>
  27. </van-swipe-item>
  28. </van-swipe>
  29. </view>
  30. <view v-if="projectList.length > 0" class="server-list">
  31. <van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" ref="list"
  32. @load="onLoad">
  33. <view v-for="item in projectList" class="server-item" @click="toServiceDetail(item.id)">
  34. <view class="img-box">
  35. <image :src="item.image" mode="aspectFill"></image>
  36. </view>
  37. <view class="server-info">
  38. <view class="server-title">{{ item.title }}</view>
  39. <view class="time-coupon">
  40. <image src="@/static/home/time-icon.png"></image>
  41. <view class="time">{{ item.times }}分钟</view>
  42. <!-- <view class="coupon">{{ item.subTitle }}</view> -->
  43. </view>
  44. <view class="price">
  45. <view class="current-price">
  46. <text class="unit"></text>{{ item.price }}
  47. </view>
  48. <view class="original-price">
  49. <text class="unit"></text>{{ item.oldPrice }}
  50. </view>
  51. </view>
  52. <view class="sales-volume">
  53. <image src="@/static/icons/icon1.png"></image>
  54. <view class="desc">已售出{{ item.payNum }}+</view>
  55. </view>
  56. </view>
  57. <view class="selective-technician">
  58. <view @click.stop="selectTechnician(item.id)" class="btn">
  59. 选择技师
  60. </view>
  61. </view>
  62. </view>
  63. </van-list>
  64. </view>
  65. <van-empty v-else image="/static/empty/data.png" image-size="400rpx" description="暂无项目" />
  66. </view>
  67. <selectArea :show="showAeraPro" @close="closeAreaPro" @select="selectArea"></selectArea>
  68. </view>
  69. </template>
  70. <script>
  71. import selectArea from '../../components/selectArea.vue';
  72. import Position from '@/utils/position.js'
  73. export default {
  74. components: {
  75. selectArea
  76. },
  77. data() {
  78. return {
  79. bannerList: [],
  80. projectList: [],
  81. queryParams: {
  82. pageNo: 1,
  83. pageSize: 10,
  84. title: ''
  85. },
  86. loading: false,
  87. finished: false,
  88. technicianList: [],
  89. showAeraPro: false,
  90. area: ''
  91. }
  92. },
  93. onShow() {
  94. this.getBanner()
  95. this.getProject()
  96. this.getLocation()
  97. },
  98. methods: {
  99. //list列表滑动到底部自动新增数据列表
  100. onLoad() {
  101. this.queryParams.pageSize += 10;
  102. this.getProject()
  103. },
  104. //获取banner
  105. getBanner() {
  106. this.$api('getBanner', {}, res => {
  107. this.bannerList = res.result;
  108. })
  109. },
  110. //获取项目列表
  111. getProject() {
  112. this.$api('getProjectList', this.queryParams, res => {
  113. if (res.code == 200) {
  114. this.projectList = res.result.records;
  115. } else {
  116. this.finished = true
  117. }
  118. if (this.queryParams.pageSize > res.result.total) {
  119. this.finished = true
  120. }
  121. this.loading = false
  122. })
  123. },
  124. //获取技师详情
  125. selectTechnician(id) {
  126. this.$api('getProjectDetail', {
  127. id
  128. }, res => {
  129. if (res.code == 200) {
  130. uni.navigateTo({
  131. url: `/pages/technician/selectTechnician?serviceId=${id}`
  132. })
  133. sessionStorage.setItem('technicianList', JSON.stringify(res.result.tenPageList))
  134. }
  135. })
  136. },
  137. //跳转技师详情
  138. toServiceDetail(id) {
  139. uni.navigateTo({
  140. url: '/pages/technician/subscribeService?id=' + id
  141. })
  142. },
  143. //显示选择地区
  144. showSelectArea() {
  145. this.showAeraPro = true;
  146. },
  147. //关闭选择地区
  148. closeAreaPro() {
  149. this.showAeraPro = false;
  150. },
  151. //选择了地区信息
  152. selectArea(area) {
  153. this.area = area;
  154. this.showAeraPro = false;
  155. //后端逻辑
  156. this.updateSessionPositon(area)
  157. },
  158. //搜索地址
  159. searchAddress() {
  160. Position.getLocation(res => {
  161. Position.selectAddress(res.longitude, res.latitude, success => {
  162. let address = this.extractProvinceAndCity(success)
  163. this.queryParams.title = address.city
  164. })
  165. })
  166. },
  167. //提取用户选择的地址信息(省市县信息)
  168. extractProvinceAndCity(res) { //提取用户选择的地址信息(省市)
  169. if (!res.address && res.name) { //用户直接选择城市的逻辑
  170. return {
  171. province: '',
  172. city: res.name
  173. };
  174. }
  175. if (res.address) { //用户选择了详细地址,要从详细地址中提取出省市县信息
  176. // 使用正则表达式匹配省市县
  177. const regex = /(?<province>[\u4e00-\u9fa5]+?省)(?<city>[\u4e00-\u9fa5]+?(?:市|自治州|盟|地区))/;
  178. const match = res.address.match(regex);
  179. if (match) { // 如果匹配成功,则返回省和市的信息
  180. return {
  181. province: match.groups.province,
  182. city: match.groups.city
  183. };
  184. }
  185. }
  186. return { //用户没选择地址就点了确定按钮
  187. province: '',
  188. city: ''
  189. }
  190. },
  191. //获取用户详细地址(省市县)
  192. getLocation() {
  193. Position.getLocationDetail().then(res => {
  194. sessionStorage.setItem("position", JSON.stringify(res))
  195. this.area = res.addressDetail.district
  196. })
  197. },
  198. //初始化用户所在地区
  199. initUserArea() {
  200. if (!sessionStorage.getItem('position')) return this.getLocation()
  201. let positionInfo = JSON.parse(sessionStorage.getItem('position'))
  202. this.area = positionInfo.addressDetail.district
  203. },
  204. //更新用户所在区域(更新区县信息)
  205. updateSessionPositon(area) {
  206. if (sessionStorage.getItem('position')) {
  207. let position = JSON.parse(sessionStorage.getItem('position'))
  208. position.addressDetail.district = area
  209. sessionStorage.setItem('position', JSON.stringify(position))
  210. }
  211. }
  212. }
  213. }
  214. </script>
  215. <style lang="scss" scoped>
  216. .home {
  217. width: 750rpx;
  218. background: #F5F5F5;
  219. margin: 0 auto;
  220. .home-top {
  221. height: 350rpx;
  222. background: linear-gradient(38deg, #4899A6, #60BDA2);
  223. padding-top: 60rpx;
  224. .search {
  225. height: 82rpx;
  226. width: 710rpx;
  227. background: #FFFFFF;
  228. margin: 0px auto;
  229. border-radius: 41rpx;
  230. box-sizing: border-box;
  231. padding: 0 15rpx;
  232. display: flex;
  233. align-items: center;
  234. justify-content: space-between;
  235. .left-area,
  236. .center-area {
  237. display: flex;
  238. align-items: center;
  239. }
  240. .left-area {
  241. max-width: 160rpx;
  242. image {
  243. flex-shrink: 0;
  244. width: 26rpx;
  245. height: 26rpx;
  246. }
  247. .area {
  248. font-size: 24rpx;
  249. display: -webkit-box;
  250. -webkit-line-clamp: 2;
  251. /* 限制显示两行 */
  252. -webkit-box-orient: vertical;
  253. overflow: hidden;
  254. text-overflow: ellipsis;
  255. color: #292929;
  256. }
  257. .parting-line {
  258. flex-shrink: 0;
  259. font-size: 26rpx;
  260. color: #ccc;
  261. margin: 0rpx 5rpx;
  262. }
  263. }
  264. .center-area {
  265. display: flex;
  266. flex-wrap: nowrap;
  267. align-items: center;
  268. width: calc(100% - 290rpx);
  269. image {
  270. width: 26rpx;
  271. height: 26rpx;
  272. }
  273. .van-field {
  274. background-color: transparent;
  275. box-sizing: border-box;
  276. height: 82rpx;
  277. line-height: 82rpx;
  278. width: calc(100% - 30rpx);
  279. padding: 0rpx 10rpx 0rpx 0rpx;
  280. input {
  281. height: 82rpx;
  282. font-size: 60rpx;
  283. }
  284. }
  285. }
  286. .right-area {
  287. .search-button {
  288. background: #60BDA2;
  289. height: 60rpx;
  290. width: 130rpx;
  291. font-size: 26rpx;
  292. border-radius: 35rpx;
  293. color: white;
  294. display: flex;
  295. align-items: center;
  296. justify-content: center;
  297. }
  298. }
  299. }
  300. }
  301. .home-content {
  302. width: calc(100% - 40rpx);
  303. margin: -240rpx 20rpx 0rpx 20rpx;
  304. .banner {
  305. box-sizing: border-box;
  306. .my-swipe {
  307. width: 100%;
  308. margin: 0px auto;
  309. border-radius: 20rpx;
  310. height: 334rpx;
  311. overflow: hidden;
  312. .van-swipe-item {
  313. width: 100%;
  314. .banner-image {
  315. width: 100%;
  316. image {
  317. width: 100%;
  318. }
  319. }
  320. }
  321. }
  322. }
  323. .server-list {
  324. padding-bottom: 80rpx;
  325. .server-item {
  326. display: flex;
  327. flex-wrap: wrap;
  328. justify-content: space-between;
  329. background: white;
  330. border-radius: 15rpx;
  331. box-sizing: border-box;
  332. padding: 15rpx;
  333. margin: 20rpx 0rpx;
  334. .img-box {
  335. width: 150rpx;
  336. height: 150rpx;
  337. border-radius: 10rpx;
  338. overflow: hidden;
  339. image {
  340. width: 100%;
  341. height: 100%;
  342. }
  343. }
  344. .server-info {
  345. display: flex;
  346. flex-direction: column;
  347. justify-content: space-around;
  348. width: calc(100% - 330rpx);
  349. box-sizing: border-box;
  350. padding: 0 10rpx;
  351. .server-title {}
  352. .time-coupon,
  353. .price {
  354. display: flex;
  355. flex-wrap: wrap;
  356. align-items: center;
  357. }
  358. .time-coupon {
  359. font-size: 26rpx;
  360. image {
  361. width: 22rpx;
  362. height: 22rpx;
  363. }
  364. .time {
  365. color: #B8B8B8;
  366. margin-left: 6rpx;
  367. }
  368. .coupon {
  369. display: flex;
  370. justify-content: center;
  371. align-items: center;
  372. background: #F29E45;
  373. color: white;
  374. width: 140rpx;
  375. height: 45rpx;
  376. border-radius: 10rpx;
  377. margin-left: 10rpx;
  378. }
  379. }
  380. .price {
  381. display: flex;
  382. align-items: center;
  383. color: #B8B8B8;
  384. .current-price {
  385. font-size: 30rpx;
  386. font-weight: 600;
  387. color: #D34430;
  388. margin-right: 5rpx;
  389. }
  390. .unit {
  391. font-size: 20rpx;
  392. }
  393. }
  394. .sales-volume {
  395. display: flex;
  396. align-items: center;
  397. color: #B8B8B8;
  398. font-size: 26rpx;
  399. image {
  400. width: 23rpx;
  401. height: 23rpx;
  402. }
  403. }
  404. }
  405. .selective-technician {
  406. display: flex;
  407. flex-wrap: wrap;
  408. align-items: center;
  409. width: 170rpx;
  410. .btn {
  411. display: flex;
  412. align-items: center;
  413. justify-content: center;
  414. height: 60rpx;
  415. width: 170rpx;
  416. border-radius: 40rpx;
  417. color: white;
  418. background: linear-gradient(170deg, #53CEAC, #5AC796);
  419. }
  420. }
  421. }
  422. }
  423. }
  424. }
  425. </style>