Files
code-bang/frontend/miniapp/pages/demand/list/index.js
T
2026-10-28 17:36:00 +08:00

410 lines
11 KiB
JavaScript

// 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'),
};
}
})