第一次提交:微信小程序前端最新完整代码
This commit is contained in:
176
pages/wodedashou/wodedashou.js
Normal file
176
pages/wodedashou/wodedashou.js
Normal file
@@ -0,0 +1,176 @@
|
||||
// pages/wodedashou/wodedashou.js
|
||||
import request from '../../utils/request.js'
|
||||
import { resolveAvatarUrl } from '../../utils/avatar.js'
|
||||
import { openPrivateChat, buildDashouPeerId } from '../../utils/im-user.js'
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
// 统计
|
||||
yaoqingzongshu: 0,
|
||||
zaixianCount: 0,
|
||||
zhengchangCount: 0,
|
||||
fengjinCount: 0,
|
||||
|
||||
// 列表
|
||||
dashouList: [],
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
hasMore: true,
|
||||
isLoading: false, // 首次加载或筛选时使用
|
||||
isLoadingMore: false, // 上拉加载更多时使用
|
||||
|
||||
// 筛选条件
|
||||
keyword: '',
|
||||
zhanghaozhuangtai: null, // null=全部, 1=正常, 2=封禁
|
||||
zaixianzhuangtai: null, // null=全部, 1=在线, 2=离线
|
||||
|
||||
// 公共资源
|
||||
ossImageUrl: '',
|
||||
defaultAvatar: ''
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.initGlobal()
|
||||
this.jiazaiShuju(true)
|
||||
this.registerNotification()
|
||||
},
|
||||
|
||||
registerNotification() {
|
||||
const comp = this.selectComponent('#global-notification')
|
||||
if (comp?.showNotification) {
|
||||
app.globalData.globalNotification = {
|
||||
show: d => comp.showNotification(d),
|
||||
hide: () => comp.hideNotification()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
initGlobal() {
|
||||
const g = app.globalData
|
||||
this.setData({
|
||||
ossImageUrl: g.ossImageUrl || '',
|
||||
defaultAvatar: (g.ossImageUrl || '') + (g.morentouxiang || '')
|
||||
})
|
||||
},
|
||||
|
||||
// 手动点击刷新按钮(重新加载第一页)
|
||||
shuaxinShuju() {
|
||||
if (this.data.isLoading) return
|
||||
this.setData({ currentPage: 1 })
|
||||
this.jiazaiShuju(true)
|
||||
},
|
||||
|
||||
// 上拉触底加载更多(同时也作为按钮点击方法)
|
||||
shanglaJiazaiGengduo() {
|
||||
if (!this.data.hasMore || this.data.isLoadingMore || this.data.isLoading) return
|
||||
this.jiazaiShuju(false)
|
||||
},
|
||||
|
||||
// 核心数据请求
|
||||
async jiazaiShuju(isFirstLoad) {
|
||||
if (isFirstLoad && this.data.isLoading) return
|
||||
if (!isFirstLoad && this.data.isLoadingMore) return
|
||||
|
||||
this.setData({
|
||||
isLoading: isFirstLoad ? true : this.data.isLoading,
|
||||
isLoadingMore: isFirstLoad ? false : true
|
||||
})
|
||||
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/yonghu/gshqyqds',
|
||||
method: 'POST',
|
||||
data: {
|
||||
page: isFirstLoad ? 1 : this.data.currentPage,
|
||||
pageSize: this.data.pageSize,
|
||||
keyword: this.data.keyword,
|
||||
zhanghaozhuangtai: this.data.zhanghaozhuangtai,
|
||||
zaixianzhuangtai: this.data.zaixianzhuangtai
|
||||
}
|
||||
})
|
||||
|
||||
if (res.data.code === 200) {
|
||||
this.handleResponse(res.data.data, isFirstLoad)
|
||||
} else {
|
||||
wx.showToast({ title: res.data.message || '加载失败', icon: 'none' })
|
||||
if (!isFirstLoad) {
|
||||
this.setData({ currentPage: this.data.currentPage - 1 })
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
wx.showToast({ title: '网络异常', icon: 'none' })
|
||||
if (!isFirstLoad) {
|
||||
this.setData({ currentPage: this.data.currentPage - 1 })
|
||||
}
|
||||
} finally {
|
||||
this.setData({
|
||||
isLoading: false,
|
||||
isLoadingMore: false
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
handleResponse(data, isFirstLoad) {
|
||||
const process = (list) => list.map(item => ({
|
||||
...item,
|
||||
touxiangUrl: this.fullAvatar(item.touxiang)
|
||||
}))
|
||||
|
||||
if (isFirstLoad) {
|
||||
this.setData({
|
||||
dashouList: process(data.dashouList || []),
|
||||
yaoqingzongshu: data.yaoqingzongshu || 0,
|
||||
zaixianCount: data.zaixianCount || 0,
|
||||
zhengchangCount: data.zhengchangCount || 0,
|
||||
fengjinCount: data.fengjinCount || 0,
|
||||
currentPage: data.hasMore ? 2 : 1,
|
||||
hasMore: data.hasMore
|
||||
})
|
||||
} else {
|
||||
this.setData({
|
||||
dashouList: [...this.data.dashouList, ...process(data.dashouList || [])],
|
||||
currentPage: this.data.currentPage + 1,
|
||||
hasMore: data.hasMore
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
fullAvatar(relative) {
|
||||
return resolveAvatarUrl(relative, app)
|
||||
},
|
||||
|
||||
// 筛选输入
|
||||
onKeywordInput(e) {
|
||||
this.setData({ keyword: e.detail.value.trim() })
|
||||
},
|
||||
|
||||
// picker 变更
|
||||
onZhanghaoChange(e) {
|
||||
const idx = parseInt(e.detail.value, 10)
|
||||
this.setData({ zhanghaozhuangtai: idx === 0 ? null : idx })
|
||||
},
|
||||
onZaixianChange(e) {
|
||||
const idx = parseInt(e.detail.value, 10)
|
||||
this.setData({ zaixianzhuangtai: idx === 0 ? null : idx })
|
||||
},
|
||||
|
||||
// 点击筛选按钮
|
||||
applyFilter() {
|
||||
if (this.data.isLoading) return
|
||||
this.setData({ currentPage: 1 })
|
||||
this.jiazaiShuju(true)
|
||||
},
|
||||
|
||||
// 联系打手(私聊)
|
||||
contactDashou(e) {
|
||||
const dashouUid = e.currentTarget.dataset.uid
|
||||
if (!dashouUid) return
|
||||
const item = this.data.dashouList.find(i => i.uid === dashouUid)
|
||||
openPrivateChat(this, {
|
||||
toUserId: buildDashouPeerId(dashouUid),
|
||||
toName: item?.nicheng || '打手',
|
||||
toAvatar: item?.touxiang || item?.touxiangUrl || '',
|
||||
})
|
||||
}
|
||||
})
|
||||
5
pages/wodedashou/wodedashou.json
Normal file
5
pages/wodedashou/wodedashou.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"global-notification": "/components/global-notification/global-notification"
|
||||
}
|
||||
}
|
||||
93
pages/wodedashou/wodedashou.wxml
Normal file
93
pages/wodedashou/wodedashou.wxml
Normal file
@@ -0,0 +1,93 @@
|
||||
<view class="page-container">
|
||||
<!-- 固定顶部区域(筛选 + 统计) -->
|
||||
<view class="fixed-header">
|
||||
<view class="header-bar">
|
||||
<text class="title">我的下级</text>
|
||||
<view class="refresh-btn" bindtap="shuaxinShuju">↻</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-box">
|
||||
<input class="search-input" placeholder="搜索ID/昵称" value="{{keyword}}" bindinput="onKeywordInput" />
|
||||
<view class="search-icon" bindtap="applyFilter">🔍</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选行 -->
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text>账号状态</text>
|
||||
<picker mode="selector" range="{{['全部','正常','封禁']}}" bindchange="onZhanghaoChange">
|
||||
<view class="picker-trigger">
|
||||
{{zhanghaozhuangtai === null ? '全部' : (zhanghaozhuangtai === 1 ? '正常' : '封禁')}}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text>在线状态</text>
|
||||
<picker mode="selector" range="{{['全部','在线','离线']}}" bindchange="onZaixianChange">
|
||||
<view class="picker-trigger">
|
||||
{{zaixianzhuangtai === null ? '全部' : (zaixianzhuangtai === 1 ? '在线' : '离线')}}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="filter-btn" bindtap="applyFilter">筛选</view>
|
||||
</view>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<view class="stats-cards">
|
||||
<view class="stat-item"><text class="stat-num">{{yaoqingzongshu}}</text><text class="stat-label">邀请总数</text></view>
|
||||
<view class="stat-item"><text class="stat-num">{{zaixianCount}}</text><text class="stat-label">在线</text></view>
|
||||
<view class="stat-item"><text class="stat-num">{{zhengchangCount}}</text><text class="stat-label">正常</text></view>
|
||||
<view class="stat-item"><text class="stat-num">{{fengjinCount}}</text><text class="stat-label">封禁</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 可滚动列表区域(占据剩余高度) -->
|
||||
<scroll-view
|
||||
class="list-scroll"
|
||||
scroll-y
|
||||
enable-back-to-top
|
||||
bindscrolltolower="shanglaJiazaiGengduo"
|
||||
lower-threshold="80"
|
||||
>
|
||||
<view class="list-container">
|
||||
<block wx:for="{{dashouList}}" wx:key="uid">
|
||||
<view class="dashou-card">
|
||||
<image class="avatar" src="{{item.touxiangUrl}}" mode="aspectFill" />
|
||||
<view class="info-right">
|
||||
<view class="row1">
|
||||
<text class="nick">{{item.nicheng}}</text>
|
||||
<view class="badges">
|
||||
<text class="badge {{item.zaixianzhuangtai==1?'online':'offline'}}">{{item.zaixianzhuangtai==1?'在线':'离线'}}</text>
|
||||
<text class="badge {{item.zhanghaozhuangtai==1?'normal':'banned'}}">{{item.zhanghaozhuangtai==1?'正常':'封禁'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="uid">ID: {{item.uid}}</text>
|
||||
<view class="row2">
|
||||
<text class="tag">接单 {{item.jiedanzongliang || 0}}</text>
|
||||
<text class="tag">成交 {{item.chengjiaozongliang || 0}}</text>
|
||||
<text class="tag">退款 {{item.tuikuanliang || 0}}</text>
|
||||
<text class="tag">余额 ¥{{item.yue || '0.00'}}</text>
|
||||
</view>
|
||||
<view class="contact-btn" data-uid="{{item.uid}}" bindtap="contactDashou">联系</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 加载更多按钮(新增) -->
|
||||
<view wx:if="{{hasMore && !isLoadingMore}}" class="load-more-btn" bindtap="shanglaJiazaiGengduo">
|
||||
加载更多
|
||||
</view>
|
||||
|
||||
<!-- 加载状态提示 -->
|
||||
<view class="load-state" wx:if="{{isLoadingMore}}"><text>加载中…</text></view>
|
||||
<view class="load-state" wx:if="{{!hasMore && dashouList.length > 0}}"><text>— 已全部加载 —</text></view>
|
||||
<view class="empty" wx:if="{{!isLoading && dashouList.length === 0}}">
|
||||
<image src="/images/empty.png" mode="aspectFit" />
|
||||
<text>暂无下级打手</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<global-notification id="global-notification" />
|
||||
</view>
|
||||
258
pages/wodedashou/wodedashou.wxss
Normal file
258
pages/wodedashou/wodedashou.wxss
Normal file
@@ -0,0 +1,258 @@
|
||||
/* 全局 */
|
||||
page {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #e4e9f2 100%);
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 固定头部区域 */
|
||||
.fixed-header {
|
||||
flex-shrink: 0;
|
||||
background: rgba(255,255,255,0.7);
|
||||
backdrop-filter: blur(20px);
|
||||
padding: 20rpx 30rpx 10rpx;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.header-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36rpx;
|
||||
color: #5a6d7e;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.refresh-btn:active { background: rgba(200,200,200,0.5); }
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-box {
|
||||
display: flex;
|
||||
margin: 15rpx 0;
|
||||
background: white;
|
||||
border-radius: 40rpx;
|
||||
padding: 8rpx 20rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 70rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
width: 70rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36rpx;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
/* 筛选行 */
|
||||
.filter-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 5rpx;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.picker-trigger {
|
||||
margin-left: 10rpx;
|
||||
background: rgba(255,255,255,0.8);
|
||||
border-radius: 20rpx;
|
||||
padding: 6rpx 20rpx;
|
||||
border: 1rpx solid #e0e0e0;
|
||||
min-width: 100rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
background: linear-gradient(135deg, #6c8cbf, #8aa5d0);
|
||||
color: white;
|
||||
border-radius: 30rpx;
|
||||
padding: 10rpx 35rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-cards {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-top: 15rpx;
|
||||
margin-bottom: 10rpx;
|
||||
padding: 25rpx 0;
|
||||
background: rgba(255,255,255,0.65);
|
||||
backdrop-filter: blur(15px);
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.stat-item { display: flex; flex-direction: column; align-items: center; }
|
||||
.stat-num { font-size: 40rpx; font-weight: 700; color: #2c3e50; }
|
||||
.stat-label { font-size: 24rpx; color: #7f8c8d; margin-top: 8rpx; }
|
||||
|
||||
/* 列表滚动区域(占据剩余高度) */
|
||||
.list-scroll {
|
||||
flex: 1;
|
||||
padding: 0 30rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 列表内部容器 */
|
||||
.list-container {
|
||||
padding-bottom: 30rpx;
|
||||
}
|
||||
|
||||
/* 打手卡片 */
|
||||
.dashou-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(255,255,255,0.75);
|
||||
backdrop-filter: blur(12px);
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.06);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.dashou-card:active { transform: scale(0.99); }
|
||||
|
||||
.avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
border: 3rpx solid rgba(255,255,255,0.8);
|
||||
box-shadow: 0 4rpx 10rpx rgba(0,0,0,0.1);
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-right { flex: 1; display: flex; flex-direction: column; }
|
||||
|
||||
.row1 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nick {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1e272e;
|
||||
max-width: 200rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badges { display: flex; gap: 10rpx; }
|
||||
.badge {
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.online { background: #27ae60; }
|
||||
.offline { background: #bdc3c7; }
|
||||
.normal { background: #2980b9; }
|
||||
.banned { background: #e74c3c; }
|
||||
|
||||
.uid { font-size: 24rpx; color: #7f8c8d; margin: 6rpx 0; }
|
||||
|
||||
.row2 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
margin: 6rpx 0;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background: #f0f3f7;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 16rpx;
|
||||
font-size: 22rpx;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
.contact-btn {
|
||||
margin-top: 8rpx;
|
||||
align-self: flex-end;
|
||||
background: linear-gradient(135deg, #f39c12, #e67e22);
|
||||
color: white;
|
||||
padding: 10rpx 28rpx;
|
||||
border-radius: 30rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 4rpx 8rpx rgba(243,156,18,0.3);
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.load-state { text-align: center; padding: 30rpx; color: #95a5a6; font-size: 24rpx; }
|
||||
|
||||
/* 加载更多按钮(新增) */
|
||||
.load-more-btn {
|
||||
width: 280rpx;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
background: rgba(108, 140, 191, 0.15);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 40rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #5a6d7e;
|
||||
border: 1rpx solid rgba(108, 140, 191, 0.3);
|
||||
margin: 20rpx auto;
|
||||
}
|
||||
|
||||
.load-more-btn:active {
|
||||
background: rgba(108, 140, 191, 0.25);
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: 80rpx;
|
||||
}
|
||||
|
||||
.empty image { width: 240rpx; height: 240rpx; opacity: 0.5; }
|
||||
.empty text { margin-top: 30rpx; color: #b0b8c0; font-size: 28rpx; }
|
||||
Reference in New Issue
Block a user