第一次提交:微信小程序前端最新完整代码

This commit is contained in:
XingQue
2026-06-12 21:55:06 +08:00
commit 84be8489b4
330 changed files with 74263 additions and 0 deletions

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

View File

@@ -0,0 +1,5 @@
{
"usingComponents": {
"global-notification": "/components/global-notification/global-notification"
}
}

View 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>

View 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; }