// pages/demand/list/index.js import { fetchDemandList } from '../../../services/demand/fetchDemand'; import { fetchConfig } from '../../../services/home/home'; import { withPromoter } from '../../../utils/promoter'; Page({ /** * 页面的初始数据 */ data: { page: 1, opType: 0, keywords: '', demandList: [], isEmpty: false, status: -1, freshPage: false, fromPreview: false, // 预览锁 hasMore: true, // 新增字段,标记是否还有更多数据 refreshing: false, // 刷新按钮是否处于加载中 tabList: [ { text: '全部', key: -1 }, { text: '招标中', key: 1 }, { text: '签约中', key: 2 }, { text: '工作中', key: 3 }, { text: '验收中', key: 4 }, { text: '结算中', key: 5 }, { text: '交易完成', key: 6 }, { text: '交易关闭', key: 7 }, ] }, // 标签页切换事件处理 tabChangeHandle(e) { const { value } = e.detail; this.setData({ status: value, page: 1, hasMore: true }); // 根据不同标签加载不同数据 this.loadDemandList(true); }, //搜索 onChangeInput(e) { const { value } = e.detail; this.setData({ keywords: value }); if (!value) { this.loadDemandList(true); } }, onChangeSearch(e) { const { value } = e.detail; if (value !== undefined) { this.setData({ keywords: value }); } this.loadDemandList(true); }, // 点击右侧刷新按钮 async onRefreshTap() { if (this.data.refreshing) return; this.setData({ refreshing: true }); await this.loadDemandList(true); this.setData({ refreshing: false }); }, //图片预览 onImageView(e) { const { images, index } = e.currentTarget.dataset; wx.previewImage({ current: images[index], urls: images, complete: () => { this.setData({ fromPreview: true }); } }); }, // 加载需求列表数据 async loadDemandList(fresh = false, options = {}) { const { silent = false } = options; // 上拉加载:请求未结束或没有下一页时直接返回,避免并发把同一页打两次 if (!fresh && (this._loading || !this.data.hasMore)) { return; } const seq = (this._listSeq || 0) + 1; this._listSeq = seq; this._loading = true; const page = fresh ? 1 : this.data.page; const { status, keywords, opType } = this.data; if (fresh) { wx.pageScrollTo({ scrollTop: 0 }); } if (!silent) { wx.showLoading({ title: '加载中', mask: true }); } try { const res = await fetchDemandList({ page, status, keywords, opType }); if (seq !== this._listSeq) { return; } if (!res || res.code !== 1 || !res.data) { wx.showToast({ title: (res && res.msg) || '加载失败', icon: 'none' }); return; } const nextList = Array.isArray(res.data.data) ? res.data.data : []; const hasMore = res.data.current_page < res.data.last_page; const start = fresh ? 0 : this.data.demandList.length; const patch = { hasMore, isEmpty: start + nextList.length === 0, }; if (hasMore) { patch.page = page + 1; } else if (fresh) { patch.page = 1; } if (fresh) { patch.demandList = nextList; } else { nextList.forEach((item, i) => { patch[`demandList[${start + i}]`] = item; }); } this.setData(patch, () => { if (seq !== this._listSeq) { return; } const indices = []; for (let i = start; i < this.data.demandList.length; i++) { indices.push(i); } this.checkDescOverflow(indices); }); } catch (err) { if (seq === this._listSeq) { console.error('Failed to load demand list:', err); wx.showToast({ title: '网络异常,请重试', icon: 'none' }); } } finally { if (seq === this._listSeq) { this._loading = false; wx.hideLoading(); } } }, /** * 保持当前分页与滚动位置重新拉取列表 * 用于「详情页/签约页操作后返回」时刷新该条数据的状态与按钮: * 一次拉回已加载的全部条目,避免像 loadDemandList(true) 那样回到第一页 */ async refreshListKeepPosition() { const { status, keywords, opType, demandList } = this.data; const loadedCount = demandList.length; if (!loadedCount) { await this.loadDemandList(true); return; } const prev = {}; demandList.forEach((item) => { prev[item.id] = { expanded: !!item.expanded, showToggle: !!item.showToggle }; }); const seq = (this._listSeq || 0) + 1; this._listSeq = seq; this._loading = true; try { const res = await fetchDemandList({ page: 1, pageSize: loadedCount, status, keywords, opType }); if (seq !== this._listSeq) { return; } if (!res || res.code !== 1 || !res.data) { return; } const raw = Array.isArray(res.data.data) ? res.data.data : []; // 保留已展开状态,避免返回列表时把「查看全文」收回去 const nextList = raw.map((item) => { const old = prev[item.id]; return { ...item, expanded: old ? old.expanded : false, showToggle: old ? old.showToggle : false, }; }); this.setData({ demandList: nextList, hasMore: res.data.current_page < res.data.last_page, isEmpty: nextList.length === 0 }, () => { if (seq !== this._listSeq) { return; } const indices = []; nextList.forEach((item, index) => { if (!prev[item.id]) { indices.push(index); } }); this.checkDescOverflow(indices); }); } catch (err) { if (seq === this._listSeq) { console.error('Failed to refresh demand list:', err); } } finally { if (seq === this._listSeq) { this._loading = false; } } }, /** * 只测量指定下标的描述高度,按路径更新 showToggle * 不改 expanded,加载下一页时已展开的条目保持展开 * @param {number[]} indices */ checkDescOverflow(indices) { if (!indices || !indices.length) { return; } const seq = this._listSeq; const query = this.createSelectorQuery(); const maxHeight = 60; // 120rpx ≈ 60px indices.forEach((index) => { query.select(`#desc-${index}`).boundingClientRect(); }); query.exec((rects) => { if (seq !== this._listSeq || !rects) { return; } const patch = {}; rects.forEach((rect, i) => { const index = indices[i]; const item = this.data.demandList[index]; if (!item) { return; } const showToggle = !!(rect && rect.height > maxHeight + 1); if (!!item.showToggle !== showToggle) { patch[`demandList[${index}].showToggle`] = showToggle; } }); if (Object.keys(patch).length) { this.setData(patch); } }); }, // 切换展开/收起 toggleDesc(e) { const index = e.currentTarget.dataset.index; const key = `demandList[${index}].expanded`; this.setData({ [key]: !this.data.demandList[index].expanded }); }, loadConfig() { fetchConfig().then( (res) => { console.log(res); if (res.code == 1) { let resData = res.data; this.setData({ tabList: resData.developTypes }); } } ); }, demandListClickHandle(e) { const { index } = e.currentTarget.dataset; const item = this.data.demandList[index]; if (!item) { return; } // 交易完成只允许发布者和接单开发者进入详情,其他人点击无反应 if (item.button && item.button.key === 'finished' && !item.isMy && !item.isMyWin) { return; } // 参与投标仅开发者可进入报价;发布者不能给自己报名。未入驻先提示,确定后再去申请 if (item.button && item.button.key === 'bid' && !item.isMy) { const role = (wx.getStorageSync('userInfo') || {}).role || ''; if (role !== 'dev') { wx.showModal({ title: '提示', content: '请先申请成为开发者', confirmText: '确定', cancelText: '取消', success: (res) => { if (!res.confirm) { return; } wx.navigateTo({ url: '/pages/developer/join/index', }); }, }); return; } } const { id } = item; const type = this.data.opType; // 大厅和我投标的默认是开发者视角;发布者看自己的需求走客户视角,避免进入报名 const opType = (type == 'all' || type == 'myQuote') && !item.isMy ? 'quote' : 'view'; //按后端返回的按钮动作标识判断(acceptPassed=验收), //命中时带标记,详情页据此弹出「先到沟通群体验后再验收」的提示 const isAccept = !!item.button && item.button.key === 'acceptPassed'; wx.navigateTo({ url: `/pages/demand/detail/index?id=${id}&opType=${opType}${isAccept ? '&fromAccept=1' : ''}`, }); }, /** * 生命周期函数--监听页面加载 */ onLoad(options) { // 分享卡片不带 opType,缺省进需求大厅;我的/我投标的仍由入口显式传入 const opType = options.opType || 'all'; this.setData({ opType }); wx.setNavigationBarTitle({ title: opType == 'all' ? '需求大厅' : (opType == 'myQuote' ? '我投标的需求' : '我发布的需求') }); opType == 'all' && this.loadConfig(); this.loadDemandList(); }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady() { }, /** * 生命周期函数--监听页面显示 */ onShow() { if (this.data.fromPreview) { // 刚从预览回来,直接跳过 this.setData({ fromPreview: false }); return; } //详情页/签约页/发货页操作成功后会写入该标记,回到列表时以接口数据刷新状态与按钮 const app = getApp(); const refreshDemandId = app && app.globalData ? app.globalData.refreshDemandId : 0; if (refreshDemandId) { //同时清掉 backAndRefresh 打的 freshPage,避免下次返回时又整页重载回到顶部 app.globalData.refreshDemandId = 0; this.setData({ freshPage: false }); this.refreshListKeepPosition(); return; } if (this.data.freshPage) { this.setData({ freshPage: false }); this.loadDemandList(true); } }, /** * 生命周期函数--监听页面隐藏 */ onHide() { }, /** * 生命周期函数--监听页面卸载 */ onUnload() { }, /** * 页面相关事件处理函数--监听用户下拉动作 */ async onPullDownRefresh() { await this.loadDemandList(true, { silent: true }); wx.stopPullDownRefresh(); }, /** * 页面上拉触底事件的处理函数 */ onReachBottom() { if (this._loading || !this.data.hasMore) { return; } this.loadDemandList(); }, /** * 用户点击右上角分享 */ onShareAppMessage() { return { title: '需求大厅', path: withPromoter('/pages/demand/list/index?opType=all'), }; } })