logic.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. import { getMyProfile } from '@/api/fulfiller/fulfiller'
  2. import { getPendingOrders, acceptOrder, getOrderCount, rejectOrderApi } from '@/api/order/subOrder'
  3. import { listAllService } from '@/api/service/list'
  4. import { getAreaStationList } from '@/api/system/areaStation'
  5. import { isLoggedIn } from '@/utils/auth'
  6. import customTabbar from '@/components/custom-tabbar/index.vue'
  7. export default {
  8. components: {
  9. customTabbar
  10. },
  11. data() {
  12. return {
  13. taskList: [],
  14. currentFilter: 'default', // default, distance, time
  15. filterCondition: '筛选条件',
  16. sortDistance: 'asc', // asc, desc
  17. sortTime: 'asc',
  18. scrollTop: 0, // Track scroll position
  19. isFilterShow: false,
  20. tempFilter: {
  21. service: null,
  22. distance: '全部',
  23. amount: '全部'
  24. },
  25. activeFilter: {
  26. service: null,
  27. distance: '全部',
  28. amount: '全部'
  29. },
  30. workStatus: 'resting', // resting | busy | disabled
  31. showConfirmModal: false,
  32. showPetModal: false,
  33. currentPetInfo: {},
  34. showRejectModal: false,
  35. rejectReason: '',
  36. currentOrder: null,
  37. showAcceptConfirmModal: false,
  38. showNavModal: false,
  39. navTargetItem: null,
  40. navTargetPointType: '',
  41. profile: null,
  42. profileLoading: false,
  43. serviceList: [],
  44. orderStats: {
  45. total: 0,
  46. reject: 0,
  47. completed: 0,
  48. price: 0
  49. }
  50. }
  51. },
  52. onPageScroll(e) {
  53. this.scrollTop = e.scrollTop;
  54. },
  55. async onLoad() {
  56. // Initial load
  57. this.checkWorkStatus();
  58. await this.loadServiceList();
  59. this.loadTaskList();
  60. },
  61. onShow() {
  62. uni.hideTabBar()
  63. this.checkWorkStatus();
  64. if (isLoggedIn()) {
  65. // 每次进入页面强制刷新所有展示数据
  66. this.loadProfile()
  67. this.loadOrderStats()
  68. this.loadTaskList()
  69. this.loadServiceList() // 确保服务配置也是最新的
  70. }
  71. },
  72. async onPullDownRefresh() {
  73. this.checkWorkStatus();
  74. try {
  75. await this.loadServiceList();
  76. const tasks = [
  77. this.loadTaskList()
  78. ];
  79. if (isLoggedIn()) {
  80. tasks.push(this.loadProfile());
  81. tasks.push(this.loadOrderStats());
  82. }
  83. await Promise.all(tasks);
  84. } catch (err) {
  85. console.error('刷新异常:', err);
  86. } finally {
  87. uni.stopPullDownRefresh();
  88. uni.showToast({ title: '刷新成功', icon: 'success' });
  89. }
  90. },
  91. methods: {
  92. async loadProfile() {
  93. if (this.profileLoading) return
  94. this.profileLoading = true
  95. try {
  96. const res = await getMyProfile()
  97. const data = res.data || null
  98. if (data) {
  99. // 以服务器返回的状态为准进行更新
  100. if (data.status) {
  101. this.workStatus = data.status;
  102. uni.setStorageSync('workStatus', data.status);
  103. }
  104. // 需求:头部的接单城市使用站点往上找到城市,而非直接显示站点
  105. if (data.stationId) {
  106. try {
  107. const stationRes = await getAreaStationList();
  108. const list = stationRes.data || [];
  109. const currentStation = list.find(i => i.id === data.stationId);
  110. if (currentStation) {
  111. // 向上溯源:直到找到 parentId 为 0 的节点(即顶层城市)
  112. let node = currentStation;
  113. while (node && node.parentId !== 0) {
  114. let parent = list.find(i => i.id === node.parentId);
  115. if (parent) {
  116. node = parent;
  117. } else {
  118. break;
  119. }
  120. }
  121. // 将溯源到的节点名称作为显示城市
  122. data.cityName = node.name;
  123. }
  124. } catch (e) {
  125. console.error('溯源城市失败:', e);
  126. }
  127. }
  128. }
  129. this.profile = data
  130. } catch (err) {
  131. console.error('获取个人信息失败:', err)
  132. } finally {
  133. this.profileLoading = false
  134. }
  135. },
  136. async loadServiceList() {
  137. try {
  138. const res = await listAllService()
  139. this.serviceList = res.data || []
  140. } catch (err) {
  141. console.error('获取服务类型失败:', err)
  142. }
  143. },
  144. async loadOrderStats() {
  145. try {
  146. const res = await getOrderCount()
  147. this.orderStats = res.data || { total: 0, reject: 0, completed: 0, price: 0 }
  148. } catch (err) {
  149. console.error('获取订单统计失败:', err)
  150. }
  151. },
  152. checkWorkStatus() {
  153. const status = uni.getStorageSync('workStatus');
  154. if (status) {
  155. this.workStatus = status;
  156. } else {
  157. // 默认状态为休息
  158. this.workStatus = 'resting';
  159. uni.setStorageSync('workStatus', 'resting');
  160. }
  161. },
  162. toggleFilter() {
  163. if (this.workStatus === 'resting') return; // Disable filter when resting? Or keep it? User didn't specify, but usually disabled. Let's keep it enabled for now as they might look at filters before working.
  164. this.isFilterShow = !this.isFilterShow;
  165. },
  166. goToWorkStatus() {
  167. uni.navigateTo({
  168. url: '/pages/home/work-status'
  169. });
  170. },
  171. startWork() {
  172. this.showConfirmModal = true;
  173. },
  174. confirmStartWork() {
  175. this.workStatus = 'busy';
  176. uni.setStorageSync('workStatus', 'busy');
  177. this.loadTaskList();
  178. this.showConfirmModal = false;
  179. uni.showToast({ title: '已开始接单', icon: 'success' });
  180. },
  181. closeConfirmModal() {
  182. this.showConfirmModal = false;
  183. },
  184. showPetProfile(item) {
  185. this.currentPetInfo = item;
  186. this.showPetModal = true;
  187. },
  188. closePetProfile() {
  189. this.showPetModal = false;
  190. },
  191. openRejectModal(item) {
  192. this.currentOrder = item;
  193. this.rejectReason = '';
  194. this.showRejectModal = true;
  195. },
  196. closeRejectModal() {
  197. this.showRejectModal = false;
  198. this.currentOrder = null;
  199. },
  200. async confirmReject() {
  201. if (!this.rejectReason.trim()) {
  202. uni.showToast({ title: '请输入拒绝理由', icon: 'none' });
  203. return;
  204. }
  205. if (!this.currentOrder?.id) return
  206. try {
  207. uni.showLoading({ title: '提交中...', mask: true });
  208. await rejectOrderApi({
  209. orderId: this.currentOrder.id,
  210. rejectReason: this.rejectReason
  211. });
  212. uni.showToast({ title: '已拒绝接单', icon: 'success' });
  213. this.showRejectModal = false;
  214. this.currentOrder = null;
  215. this.loadTaskList();
  216. this.loadOrderStats();
  217. } catch (err) {
  218. console.error('拒绝接单失败:', err);
  219. uni.showToast({ title: '操作失败', icon: 'none' });
  220. } finally {
  221. uni.hideLoading();
  222. }
  223. },
  224. openAcceptModal(item) {
  225. this.currentOrder = item;
  226. this.showAcceptConfirmModal = true;
  227. },
  228. closeAcceptModal() {
  229. this.showAcceptConfirmModal = false;
  230. this.currentOrder = null;
  231. },
  232. async confirmAccept() {
  233. if (!this.currentOrder?.id) return
  234. try {
  235. await acceptOrder(this.currentOrder.id)
  236. uni.showToast({ title: '接单成功', icon: 'success' })
  237. this.showAcceptConfirmModal = false
  238. this.currentOrder = null
  239. this.loadTaskList()
  240. this.loadProfile()
  241. this.loadOrderStats()
  242. } catch (err) {
  243. console.error('接单失败:', err)
  244. uni.showToast({ title: '接单失败', icon: 'none' })
  245. }
  246. },
  247. openNavigation(item, pointType) {
  248. this.navTargetItem = item;
  249. this.navTargetPointType = pointType;
  250. this.showNavModal = true;
  251. },
  252. closeNavModal() {
  253. this.showNavModal = false;
  254. },
  255. chooseMap(mapType) {
  256. let item = this.navTargetItem;
  257. let pointType = this.navTargetPointType;
  258. // 起 -> fromAddress ; 终 -> toAddress
  259. let name = pointType === 'start' ? (item.fromAddress || '起点') : (item.toAddress || '终点');
  260. let address = pointType === 'start' ? (item.fromAddress || '起点地址') : (item.toAddress || '终点地址');
  261. let latitude = pointType === 'start' ? Number(item.fromLat) : Number(item.toLat);
  262. let longitude = pointType === 'start' ? Number(item.fromLng) : Number(item.toLng);
  263. this.showNavModal = false;
  264. // 统一定义打开地图的函数
  265. const navigateTo = (lat, lng, addrName, addrDesc) => {
  266. uni.openLocation({
  267. latitude: lat,
  268. longitude: lng,
  269. name: addrName,
  270. address: addrDesc || '无法获取详细地址',
  271. success: function () {
  272. console.log('打开导航成功: ' + mapType);
  273. },
  274. fail: function (err) {
  275. console.error('打开导航失败:', err);
  276. uni.showToast({ title: '打开地图失败', icon: 'none' });
  277. }
  278. });
  279. };
  280. // 如果有目标经纬度,直接打开
  281. if (latitude && longitude && !isNaN(latitude) && !isNaN(longitude)) {
  282. navigateTo(latitude, longitude, name, address);
  283. } else {
  284. // 如果没有经纬度,按照需求:使用自己当前的经纬度,然后搜索 fromAddress 或者 toAddress
  285. uni.showLoading({ title: '获取当前位置...', mask: true });
  286. uni.getLocation({
  287. type: 'gcj02',
  288. success: (res) => {
  289. uni.hideLoading();
  290. // 使用用户当前经纬度作为锚点打开地图,展示目标地址信息
  291. navigateTo(res.latitude, res.longitude, name, address);
  292. },
  293. fail: (err) => {
  294. uni.hideLoading();
  295. console.error('获取地理位置失败:', err);
  296. uni.showToast({ title: '无法获取当前位置信息', icon: 'none' });
  297. }
  298. });
  299. }
  300. },
  301. selectService(type) {
  302. this.tempFilter.service = type;
  303. },
  304. selectDistance(type) {
  305. this.tempFilter.distance = type;
  306. },
  307. selectAmount(type) {
  308. this.tempFilter.amount = type;
  309. },
  310. resetFilter() {
  311. this.tempFilter = {
  312. service: null,
  313. distance: '全部',
  314. amount: '全部'
  315. };
  316. },
  317. confirmFilter() {
  318. this.activeFilter = { ...this.tempFilter };
  319. this.isFilterShow = false;
  320. this.loadTaskList();
  321. },
  322. closeFilter() {
  323. this.isFilterShow = false;
  324. },
  325. goToDetail(item) {
  326. console.log('Go to detail', item);
  327. },
  328. async loadTaskList() {
  329. try {
  330. const params = {
  331. service: this.activeFilter.service,
  332. minPrice: this.getMinPrice(),
  333. maxPrice: this.getMaxPrice(),
  334. pageNum: 1,
  335. pageSize: 20
  336. }
  337. const res = await getPendingOrders(params)
  338. this.taskList = (res.rows || []).map(item => this.transformOrder(item))
  339. } catch (err) {
  340. console.error('获取订单列表失败:', err)
  341. uni.showToast({ title: '加载失败', icon: 'none' })
  342. this.taskList = []
  343. }
  344. },
  345. getMinPrice() {
  346. const amount = this.activeFilter.amount
  347. if (amount === '100以下') return 0
  348. if (amount === '100-200') return 10000
  349. if (amount === '200-500') return 20000
  350. if (amount === '500以上') return 50000
  351. return undefined
  352. },
  353. getMaxPrice() {
  354. const amount = this.activeFilter.amount
  355. if (amount === '100以下') return 10000
  356. if (amount === '100-200') return 20000
  357. if (amount === '200-500') return 50000
  358. return undefined
  359. },
  360. transformOrder(item) {
  361. const service = this.serviceList.find(s => s.id === item.service)
  362. const serviceText = service?.name || '未知'
  363. const serviceIcon = service?.iconUrl || ''
  364. const mode = service?.mode || 0
  365. const isRoundTrip = mode === 1
  366. return {
  367. id: item.id,
  368. type: isRoundTrip ? 1 : item.service,
  369. typeText: serviceText,
  370. typeIcon: serviceIcon,
  371. price: (item.price / 100).toFixed(2),
  372. timeLabel: '服务时间',
  373. time: item.serviceTime,
  374. petAvatar: item.petAvatar || '/static/dog.png',
  375. petAvatarUrl: item.petAvatarUrl || '',
  376. petName: item.petName,
  377. petBreed: item.breed,
  378. petGender: 'M',
  379. petAge: '',
  380. petWeight: '',
  381. petPersonality: '',
  382. petHobby: '',
  383. petRemark: '',
  384. petTags: [],
  385. petLogs: [],
  386. startLocation: item.fromAddress || '暂无起点',
  387. startAddress: item.fromAddress || '',
  388. fromAddress: item.fromAddress || '',
  389. fromLat: item.fromLat,
  390. fromLng: item.fromLng,
  391. startDistance: '0km',
  392. endLocation: (item.customerName || item.contact || '') + ' ' + (item.customerPhone || ''),
  393. endAddress: item.toAddress || '',
  394. toAddress: item.toAddress || '',
  395. toLat: item.toLat,
  396. toLng: item.toLng,
  397. endDistance: '0km',
  398. serviceContent: '',
  399. remark: item.remark || ''
  400. }
  401. },
  402. setFilter(type) {
  403. this.currentFilter = type;
  404. if (type === 'distance') {
  405. this.sortDistance = this.sortDistance === 'asc' ? 'desc' : 'asc';
  406. uni.showToast({ title: `按距离${this.sortDistance === 'asc' ? '升序' : '降序'}`, icon: 'none' });
  407. } else if (type === 'time') {
  408. this.sortTime = this.sortTime === 'asc' ? 'desc' : 'asc';
  409. uni.showToast({ title: `按时间${this.sortTime === 'asc' ? '升序' : '降序'}`, icon: 'none' });
  410. }
  411. },
  412. showFilterDropdown() {
  413. this.toggleFilter();
  414. }
  415. }
  416. }