index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. <template>
  2. <view class="content">
  3. <view :style="{ marginTop: statusBarHeight }"></view>
  4. <swiper class="screen-swiper" circular :indicator-dots="bannerConfig.indicatorDots"
  5. :autoplay="bannerConfig.autoplay" :interval="bannerConfig.interval" :duration="bannerConfig.duration">
  6. <swiper-item v-for="(item, index) in bannerData" :key="index">
  7. <image :src="item.image" mode="aspectFill"></image>
  8. </swiper-item>
  9. </swiper>
  10. <uni-card class="navigate_card">
  11. <uni-notice-bar showIcon :text="noticeData" backgroundColor="#DCFCF1" color="#07D69E" :speed="80"
  12. class="text-sm">
  13. <image mode="aspectFit" src="/static/logo.png" class="notice_icon"></image>
  14. </uni-notice-bar>
  15. <view class="badge_card_wrap">
  16. <view class="badge_card_avatar">
  17. <image class="card_thumbnail" :src="userData?.js?.avatar || '/static/account/avatar.png'"
  18. mode="aspectFill"
  19. style="width: 200upx; height: 200upx; border: 4px solid #ccc0cb; border-radius: 50%" />
  20. <view
  21. style="font-size: 18px; font-weight: bold; font-family: cursive; padding: 20upx; color: #333; height: 90upx">
  22. {{ userData?.js?.name }}
  23. </view>
  24. <view class="location_wrap">
  25. <image src="/static/order/location.png" mode="aspectFill" class="location_icon"></image>
  26. <text class="location_text">
  27. {{ address?.city? `${address?.province}${address?.city}${address.district}${address.street}` : emptyAddress }}
  28. </text>
  29. </view>
  30. <uni-tag :circle="true" :text="userData?.js ? (userData?.js?.is_work ? '营业' : '休息') : '未登录'"
  31. :type="userData?.js ? (userData?.js?.is_work ? 'warning' : '') : 'error'" size="small"
  32. style="position: absolute" />
  33. </view>
  34. <view class="badge_card_content">
  35. <view style="display: flex; justify-content: space-between; padding: 15upx 90upx">
  36. <uni-tag text="个人信息" type="primary" />
  37. <view>
  38. <text style="color: #21925e"
  39. @tap.stop="handleToUserInfo">{{ userData?.js?.js_status == 1 ? '认证通过' : '立即前往' }}</text>
  40. </view>
  41. </view>
  42. <view style="display: flex; justify-content: space-between; padding: 15upx 90upx">
  43. <uni-tag text="技师认证" type="primary" />
  44. <view>
  45. <text style="color: #21925e"
  46. @tap.stop="handleToCertification">{{ userData?.js?.js_status == 1 ? '认证通过' : '立即前往' }}</text>
  47. </view>
  48. </view>
  49. <view style="display: flex; justify-content: space-between; padding: 15upx 90upx">
  50. <view style="width: 200upx">
  51. <uni-tag text="服务项目" type="primary" />
  52. </view>
  53. <view><text style="color: #21925e" @tap.stop="handleToProject">立即前往</text></view>
  54. </view>
  55. </view>
  56. <view class="badge_card_action">
  57. <button style="background: #f5a625; color: #fff; border-radius: 24upx; width: 160upx"
  58. class="mini-btn" type="default" size="mini" @tap="handlePostSite">
  59. 定 位
  60. </button>
  61. <button style="color: #fff; border-radius: 24upx; width: 160upx"
  62. :style="{ background: userData?.js?.is_work ? '#E43D33' : '#07d69e' }" class="mini-btn"
  63. type="default" size="mini" @tap="handleWork">
  64. {{ userData?.js?.is_work ? '下 钟' : '上 钟' }}
  65. </button>
  66. </view>
  67. </view>
  68. <uni-grid :column="3" :show-border="false" :square="false" v-if="false">
  69. <uni-grid-item v-for="(item, index) in navigateData" :index="index" :key="index">
  70. <view class="grid-item-box" style="background-color: #fff">
  71. <image mode="aspectFit" :src="item?.icon" class="navigate_icon"></image>
  72. <text class="navigate_title text-sm">{{ item?.name }}</text>
  73. </view>
  74. </uni-grid-item>
  75. </uni-grid>
  76. </uni-card>
  77. <view style="position: relative; top: -60upx">
  78. <image mode="widthFix" src="/static/common/slogan.png" style="width: 100%; padding: 0 20upx; z-index: 0">
  79. </image>
  80. </view>
  81. <swiper class="screen-swiper" circular :indicator-dots="bannerConfig.indicatorDots"
  82. :autoplay="bannerConfig.autoplay" :interval="bannerConfig.interval" :duration="bannerConfig.duration"
  83. style="margin: 0 20upx; min-height: 200upx; height: 200upx; position: relative; top: -40upx" v-if="false">
  84. <swiper-item v-for="(item, index) in subBannerData" :key="index">
  85. <image :src="item.image" mode="aspectFill" style="border-radius: 20upx"></image>
  86. </swiper-item>
  87. </swiper>
  88. <uni-grid :column="2" :showBorder="false" style="padding: 0 10upx; position: relative; top: -20upx"
  89. v-if="false">
  90. <uni-grid-item v-for="(item, index) in goodsData" :index="index" :key="index">
  91. <view style="margin: 10upx; overflow: hidden; border-radius: 10upx">
  92. <image mode="aspectFill" :src="item?.icon"></image>
  93. </view>
  94. </uni-grid-item>
  95. </uni-grid>
  96. <view class="footer">
  97. <image mode="widthFix" src="/static/common/footer.png" />
  98. </view>
  99. <callAlarm />
  100. </view>
  101. </template>
  102. <script setup>
  103. import { ref, reactive } from 'vue';
  104. import { onLoad, onShow, onPullDownRefresh } from '@dcloudio/uni-app';
  105. import request from '/common/request.js';
  106. import getLocation from '/common/getLocation.js';
  107. import callAlarm from '/components/callAlarm.vue';
  108. const statusBarHeight = ref(0);
  109. const bannerConfig = reactive({
  110. indicatorDots: false,
  111. autoplay: true,
  112. interval: 5000,
  113. duration: 500
  114. });
  115. const address = ref({});
  116. const emptyAddress = ref('未获得定位!请点击定位更换地点');
  117. const bannerData = ref();
  118. const subBannerData = ref();
  119. const noticeData = ref('涉嫌私下交易,禁止技师接单的处罚通知!');
  120. const goodsData = ref();
  121. const navigateData = ref([{
  122. name: '技师认证',
  123. icon: '/static/index/verify.png'
  124. },
  125. {
  126. name: '开通服务',
  127. icon: '/static/index/project.png'
  128. },
  129. {
  130. name: '新手教程',
  131. icon: '/static/index/question.png'
  132. }
  133. ]);
  134. const userData = ref();
  135. const handleWork = () => {
  136. if (!userData.value) {
  137. handleToLogin();
  138. return;
  139. }
  140. if (userData.value?.js?.js_status != 1) {
  141. if (!userData.value?.js?.js_status == 2) {
  142. uni.showToast({
  143. title: '请重新进行技师信息认证',
  144. duration: 1500,
  145. icon: 'none'
  146. });
  147. return;
  148. }
  149. uni.showToast({
  150. title: '请先进行技师信息认证并等待审核结果',
  151. duration: 1500,
  152. icon: 'none'
  153. });
  154. return;
  155. }
  156. uni.showLoading();
  157. request({
  158. url: '/api/userjs/setWorkTime',
  159. method: 'POST',
  160. data: {
  161. is_work: !userData.value?.js?.is_work
  162. },
  163. success: (res) => {
  164. console.log('上下钟', res);
  165. uni.showToast({
  166. title: res.data.msg,
  167. duration: 1500,
  168. icon: 'none'
  169. });
  170. if (res.data?.code == 1) {
  171. setTimeout(() => {
  172. getUserInfo();
  173. }, 1000);
  174. }
  175. },
  176. fail: () => {},
  177. complete: () => {
  178. uni.stopPullDownRefresh();
  179. setTimeout(() => {
  180. uni.hideLoading();
  181. }, 1500);
  182. }
  183. });
  184. };
  185. const handlePostSite = async () => {
  186. if (!userData.value) {
  187. handleToLogin();
  188. return;
  189. }
  190. const new_address = await getLocation();
  191. await getGeocode(new_address?.location?.lat, new_address?.location?.lng);
  192. address.value = uni.getStorageSync('address');
  193. console.log('获取定位', new_address);
  194. const latitude = new_address?.location?.lat;
  195. const longitude = new_address?.location?.lng;
  196. if (!latitude || !longitude) {
  197. uni.showToast({
  198. title: '获取位置失败,请重新定位',
  199. duration: 1500,
  200. icon: 'none'
  201. });
  202. return;
  203. }
  204. uni.showLoading();
  205. request({
  206. url: '/api/userjs/addPostSite',
  207. method: 'POST',
  208. data: {
  209. lat: latitude,
  210. long: longitude,
  211. nation: new_address?.nation,
  212. province: new_address?.province,
  213. city: new_address?.city,
  214. district: new_address?.district,
  215. street: new_address?.street,
  216. street_number: new_address?.street_number
  217. },
  218. success: (res) => {
  219. console.log('添加定位', res);
  220. if (res.data?.code == 1) {
  221. uni.showToast({
  222. title: res.data?.msg,
  223. duration: 1500,
  224. icon: 'none'
  225. });
  226. // getGeocode(latitude, longitude);
  227. }
  228. },
  229. fail: () => {},
  230. complete: () => {
  231. setTimeout(() => {
  232. uni.hideLoading();
  233. }, 1500);
  234. }
  235. });
  236. };
  237. const handleToLogin = () => {
  238. uni.navigateTo({
  239. url: '/pages/account/login'
  240. });
  241. };
  242. const handleToUserInfo = () => {
  243. uni.navigateTo({
  244. url: '/pages/account/setting'
  245. });
  246. };
  247. const handleToCertification = () => {
  248. uni.navigateTo({
  249. url: '/pages/account/certification'
  250. });
  251. };
  252. const handleToProject = () => {
  253. uni.switchTab({
  254. url: '/pages/project/index'
  255. });
  256. };
  257. onLoad((option) => {
  258. statusBarHeight.value = uni.getSystemInfoSync().statusBarHeight;
  259. initData();
  260. });
  261. onShow((option) => {
  262. uni.$emit('realtimeOrderOpen', false);
  263. getUserInfo();
  264. getPostSite();
  265. });
  266. const initData = () => {
  267. const addressStorage = uni.getStorageSync('address');
  268. if (addressStorage) address.value = addressStorage;
  269. getInitData();
  270. };
  271. const getPostSite = () => {
  272. request({
  273. url: '/api/userjs/getPostSite',
  274. method: 'POST',
  275. success: (res) => {
  276. console.log('技师定位数据', res);
  277. console.log('经纬度', res.data?.data?.lat);
  278. console.log('经纬度', res.data?.data?.lng);
  279. if (res.data?.code == 1) {
  280. const latitude = res.data?.data?.lat;
  281. const longitude = res.data?.data?.lng;
  282. if (!address.value?.location || address.value.location.lat !== latitude || address
  283. .value.location.lng !== longitude)
  284. getGeocode(latitude, longitude);
  285. // uni.request({
  286. // url: 'https://restapi.amap.com/v3/geocode/regeo?parameters',
  287. // method: 'GET',
  288. // data: {
  289. // key: '3c813127122a372b7b0a6f0db7eb911e',
  290. // location: `${longitude},${latitude}`
  291. // },
  292. // success: (res) => {
  293. // console.log('高德地址', res);
  294. // address.value = res.data?.regeocode?.formatted_address;
  295. // }
  296. // });
  297. }
  298. },
  299. fail: () => {},
  300. complete: () => {}
  301. });
  302. };
  303. const getGeocode = async (latitude, longitude) => {
  304. return new Promise((resolve, reject) => {
  305. return uni.request({
  306. // url: 'https://api.luokuang.com/v2/search/reverse?ak=EE172078158336222860E89C41DA444414EB02CEE4BACDC69FDNZOH2XLMU6557&radius=&lon=117.124909&lat=39.133927&categories=',
  307. url: `https://api.luokuang.com/v2/search/reverse?ak=EE1720783281948228787B497622B814F58816BB392367AA4EEYF1W3DBFR6557&lon=${longitude}&lat=${latitude}&size=1&coord_type=WGS84`,
  308. success(res) {
  309. console.log('逆编译res', res)
  310. const result = res.data?.result[0]
  311. ?.properties;
  312. address.value.province = result?.province
  313. address.value.city = result?.city
  314. address.value.district = result?.county
  315. address.value.street = result?.street
  316. address.value.location = {
  317. lng: longitude,
  318. lat: latitude
  319. };
  320. console.log('address', address.value)
  321. uni.setStorageSync('address', address
  322. .value);
  323. resolve(res)
  324. },
  325. fail: (err) => reject(err),
  326. })
  327. });
  328. return;
  329. let key = 'd8ee08d704acd692b06fc465d2eb6106'; // 高德WebKey
  330. // // #ifdef APP-PLUS
  331. // const system = uni.getSystemInfoSync(); // 获取系统信息
  332. // if (system.platform === "android")
  333. // key = "d8ee08d704acd692b06fc465d2eb6106"; // "e7703ba0579394ad136d69fb5c8e9920";
  334. // else
  335. // key = "1b8f006c331a6ac3f2d4df500c5843d2";
  336. // // #endif
  337. uni.request({
  338. url: 'https://restapi.amap.com/v3/geocode/regeo?parameters',
  339. method: 'GET',
  340. data: {
  341. key,
  342. location: `${longitude},${latitude}`
  343. },
  344. success: (res) => {
  345. console.log('高德地址', res);
  346. address.value = res.data?.regeocode?.formatted_address;
  347. }
  348. });
  349. };
  350. const getInitData = () => {
  351. uni.showLoading();
  352. request({
  353. url: '/api/js/index',
  354. success: (res) => {
  355. console.log('技师index数据', res);
  356. if (res.data?.code == 1) {
  357. bannerData.value = res.data?.data?.banner;
  358. goodsData.value = res.data?.data?.goods;
  359. subBannerData.value = res.data?.data?.banner2;
  360. // navigateData.value = res.data?.data?.nav;
  361. }
  362. // that.arr = res.data.data.goods;
  363. // that.banner = res.data.data.banner;
  364. // that.banner2 = res.data.data.banner2;
  365. // that.nav = res.data.data.nav;
  366. // that.userInfo = res.data.data;
  367. },
  368. fail: () => {},
  369. complete: () => {
  370. uni.stopPullDownRefresh();
  371. uni.hideLoading();
  372. }
  373. });
  374. };
  375. const getUserInfo = () => {
  376. uni.showLoading();
  377. request({
  378. url: '/api/user/getInfo',
  379. method: 'GET',
  380. success: (res) => {
  381. console.log('客户信息', res);
  382. if (res.data.code == 1) {
  383. userData.value = res.data?.data ?? {};
  384. uni.$emit('fastOrderNum', true);
  385. uni.$emit('receiveOrdernum', true);
  386. } else if (res.data.code == 10001) {
  387. uni.removeStorageSync('token');
  388. uni.removeStorageSync('id');
  389. uni.$emit('fastOrderNum', false);
  390. uni.$emit('receiveOrdernum', false);
  391. }
  392. },
  393. fail: () => {},
  394. complete: () => {
  395. uni.hideLoading();
  396. uni.stopPullDownRefresh();
  397. }
  398. });
  399. };
  400. </script>
  401. <style lang="scss" scoped>
  402. .uni-noticebar {
  403. border: 1upx solid #07d69e33;
  404. border-radius: 8upx;
  405. }
  406. .grid-item-box {
  407. flex: 1;
  408. /* #ifndef APP-NVUE */
  409. display: flex;
  410. /* #endif */
  411. flex-direction: column;
  412. align-items: center;
  413. justify-content: center;
  414. padding: 30upx 0 0;
  415. .navigate_icon {
  416. width: 100upx;
  417. height: 100upx;
  418. }
  419. .navigate_title {
  420. // font-size: 29upx;
  421. color: #333333;
  422. padding: 10upx 0 10upx;
  423. }
  424. }
  425. .content {
  426. .navigate_card {
  427. position: relative;
  428. top: -40upx;
  429. background: linear-gradient(to bottom, #ffffff55, #fff);
  430. padding: 0upx !important;
  431. margin: 0 20upx !important;
  432. z-index: 1;
  433. .uni-noticebar {
  434. margin-bottom: 0;
  435. }
  436. .category_content {
  437. display: flex;
  438. justify-content: space-around;
  439. margin-top: 24upx;
  440. .category_item_wrap {
  441. display: flex;
  442. flex-direction: column;
  443. align-items: center;
  444. .category_icon {
  445. width: 100upx;
  446. height: 100upx;
  447. border-radius: 64upx;
  448. }
  449. .category_title {
  450. padding: 16upx 0 8upx;
  451. font-weight: 400;
  452. color: #333333;
  453. }
  454. }
  455. }
  456. }
  457. .badge_card_wrap {
  458. position: relative;
  459. width: 668upx;
  460. height: 1070upx;
  461. background: url('/static/index/badge_card_bg.jpg') center center no-repeat;
  462. background-size: contain;
  463. border-radius: 24upx;
  464. margin: 20upx auto;
  465. box-shadow: 5upx 5upx 10upx 0upx rgba(26, 26, 26, 0.1);
  466. .badge_card_avatar {
  467. position: absolute;
  468. top: 200upx;
  469. width: 100%;
  470. display: flex;
  471. justify-content: center;
  472. flex-direction: column;
  473. align-items: center;
  474. }
  475. .badge_card_content {
  476. position: absolute;
  477. top: 600upx;
  478. width: 100%;
  479. }
  480. .badge_card_action {
  481. position: absolute;
  482. width: 100%;
  483. display: flex;
  484. justify-content: space-evenly;
  485. bottom: 50px;
  486. uni-button {
  487. &::after {
  488. border: 0;
  489. }
  490. }
  491. }
  492. }
  493. .location_wrap {
  494. display: flex;
  495. align-items: center;
  496. max-width: 600upx;
  497. height: 100upx;
  498. overflow: hidden;
  499. .location_icon {
  500. width: 32upx;
  501. height: 32upx;
  502. }
  503. .location_text {
  504. margin-left: 10upx;
  505. max-width: 550upx;
  506. }
  507. }
  508. }
  509. </style>