提交 0c77d7c8 作者: 毛细亚

准备明天上线的内容

上级 d1c161d2
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><meta http-equiv="pragma" content="no-cache"><meta http-equiv="cache-control" content="no-cache"><meta http-equiv="expires" content="0"><meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9"/><title>company_app</title><script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script><script defer="defer" src="static/js/chunk-vendors.fc0c503d.js"></script><script defer="defer" src="static/js/app.d9997098.js"></script><link href="static/css/chunk-vendors.8e901099.css" rel="stylesheet"><link href="static/css/app.1a206877.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but company_app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
\ No newline at end of file
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><meta http-equiv="pragma" content="no-cache"><meta http-equiv="cache-control" content="no-cache"><meta http-equiv="expires" content="0"><meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9"/><title>company_app</title><script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script><script defer="defer" src="static/js/chunk-vendors.fc0c503d.js"></script><script defer="defer" src="static/js/app.8d93f428.js"></script><link href="static/css/chunk-vendors.8e901099.css" rel="stylesheet"><link href="static/css/app.b2aba196.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but company_app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
\ No newline at end of file
# v-scroll 下拉加载更多指令
## 功能概述
`v-scroll` 是一个用于实现无限滚动加载的 Vue 自定义指令。当用户滚动到页面底部或接近底部时,自动触发加载更多数据的函数,适用于列表分页加载场景。
## 特性
- 自动检测滚动容器(支持窗口和自定义滚动容器)
- 使用节流控制滚动事件触发频率,提高性能
- 支持自定义触发距离
- 支持禁用功能
- 支持移动端触摸事件
- 自动处理内容不足一屏的情况
## 参数说明
### 指令值
- **类型**`Function`
- **必填**:是
- **说明**:滚动到底部时触发的加载更多函数
### 指令参数
- **语法**`v-scroll:distance`
- **类型**`Number`
- **默认值**`30`(单位:px)
- **说明**:距离底部多少像素时触发加载更多函数
### 修饰符
- **disabled**:禁用滚动加载功能
- **语法**`v-scroll.disabled`
- **说明**:设置后将不会触发加载更多函数
## 使用方法
### 基本用法
```vue
<template>
<div v-scroll="loadMore" class="list-container">
<div v-for="(item, index) in list" :key="index" class="list-item">
{{ item.title }}
</div>
<div v-if="loading" class="loading">加载中...</div>
<div v-if="noMore" class="no-more">没有更多数据</div>
</div>
</template>
<script>
export default {
data() {
return {
list: [],
page: 1,
pageSize: 10,
loading: false,
noMore: false
}
},
mounted() {
// 初始加载第一页数据
this.getList()
},
methods: {
// 加载更多函数,将作为 v-scroll 指令的值
loadMore() {
// 如果正在加载或没有更多数据,则不执行
if (this.loading || this.noMore) return
this.page++
this.getList()
},
async getList() {
this.loading = true
try {
const res = await this.fetchData(this.page, this.pageSize)
if (res.data && res.data.length > 0) {
this.list = [...this.list, ...res.data]
} else {
this.noMore = true
}
} catch (error) {
console.error('加载数据失败', error)
} finally {
this.loading = false
}
},
fetchData(page, pageSize) {
// 实际项目中替换为真实 API 调用
return this.$api.getList({ page, pageSize })
}
}
}
</script>
<style scoped>
.list-container {
height: 500px; /* 设置容器高度以启用滚动 */
overflow-y: auto;
max-width: 360px; /* 适配企业微信侧边栏 */
margin: 0 auto;
}
</style>
```
### 自定义触发距离
通过参数设置距离底部多少像素时触发加载:
```html
<!-- 距离底部 50px 时触发加载更多 -->
<div v-scroll:50="loadMore" class="list-container">
<!-- 列表内容 -->
</div>
```
### 禁用滚动加载
使用 disabled 修饰符禁用滚动加载功能:
```html
<!-- 禁用滚动加载功能 -->
<div v-scroll.disabled="loadMore" class="list-container">
<!-- 列表内容 -->
</div>
```
### 动态控制启用/禁用
结合 Vue 的条件表达式动态控制滚动加载:
```html
<!-- 根据 isLoading 状态动态启用/禁用 -->
<div v-scroll:30="isLoading ? null : loadMore" class="list-container">
<!-- 列表内容 -->
</div>
<!-- 或使用对象语法 -->
<div v-scroll="{ loadMore: loadMoreFn, disabled: isLoading }" class="list-container">
<!-- 列表内容 -->
</div>
```
## 注意事项
1. **滚动容器设置**
- 确保滚动容器有固定高度或最大高度,并设置 `overflow-y: auto``overflow-y: scroll`
- 指令会自动检测最近的滚动容器,如果没有找到,则使用 window 作为滚动容器
2. **性能优化**
- 指令内部已使用节流函数控制滚动事件触发频率,默认为 200ms
- 大数据列表建议使用虚拟滚动结合本指令使用
3. **移动端适配**
- 已支持触摸事件,在移动端也能正常工作
- 在企业微信环境中,最大宽度建议设置为 360px
4. **加载状态处理**
- 在 loadMore 函数中应该有加载状态控制,防止重复触发
- 推荐使用 loading 和 noMore 两个状态分别控制加载中和无更多数据
5. **初始内容不足一屏**
- 指令会在初始化时检查一次是否需要加载更多,处理内容不足一屏的情况
- 初始检查会延迟 50ms 执行,确保 DOM 渲染完成
## 示例场景
### 商品列表无限加载
```vue
<template>
<div class="product-list" v-scroll="loadMoreProducts">
<product-card
v-for="product in products"
:key="product.id"
:product="product"
/>
<div v-if="loading" class="loading-indicator">
<i class="el-icon-loading"></i> 加载中...
</div>
<div v-if="noMore && !loading" class="no-more">
没有更多商品了
</div>
</div>
</template>
```
### 聊天记录上拉加载历史消息
```vue
<template>
<div class="chat-container" v-scroll:50="loadHistoryMessages">
<div class="message-list">
<message-item
v-for="msg in messages"
:key="msg.id"
:message="msg"
/>
</div>
</div>
</template>
```
## 技术实现
该指令基于以下核心实现:
1. 使用 `throttle` 节流函数控制滚动事件触发频率
2. 自动检测并绑定最近的滚动容器
3. 精确计算滚动位置,支持 window 和自定义容器两种情况
4. 完整支持 Vue 指令生命周期(inserted, update, unbind)
\ No newline at end of file
<template>
<div class="scroll-demo">
<h2 class="demo-title">下拉加载更多示例</h2>
<!-- 使用v-scroll指令的列表容器 -->
<div
v-scroll="loadMore"
class="list-container"
ref="listContainer"
>
<!-- 列表项 -->
<div
v-for="(item, index) in list"
:key="index"
class="list-item"
>
<div class="item-avatar">
<img :src="item.avatar" alt="头像">
</div>
<div class="item-content">
<div class="item-title">{{item.title}}</div>
<div class="item-desc">{{item.desc}}</div>
</div>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="loading-status">
<i class="el-icon-loading"></i> 加载中...
</div>
<!-- 无更多数据提示 -->
<div v-if="noMore && !loading" class="no-more">
— 没有更多数据了 —
</div>
</div>
<!-- 控制面板 -->
<div class="control-panel">
<el-switch
v-model="disabled"
active-text="禁用加载"
inactive-text="启用加载"
/>
<el-button
@click="resetList"
type="primary"
size="small"
>
重置列表
</el-button>
</div>
</div>
</template>
<script>
export default {
name: 'ScrollDemo',
data() {
return {
list: [],
page: 1,
pageSize: 10,
loading: false,
noMore: false,
disabled: false
}
},
computed: {
// 根据disabled状态动态计算加载函数
loadMoreHandler() {
return this.disabled ? null : this.loadMore
}
},
mounted() {
// 初始加载第一页数据
this.getList()
},
methods: {
// 加载更多数据
loadMore() {
// 如果正在加载、已禁用或没有更多数据,则不执行
if (this.loading || this.disabled || this.noMore) return
this.page++
this.getList()
},
// 获取列表数据
async getList() {
this.loading = true
try {
// 模拟API请求延迟
await this.sleep(800)
// 模拟API请求返回数据
const res = await this.mockApi(this.page, this.pageSize)
if (res.data && res.data.length > 0) {
// 追加新数据到列表
this.list = [...this.list, ...res.data]
} else {
// 设置无更多数据标记
this.noMore = true
}
} catch (error) {
console.error('加载数据失败', error)
this.$message.error('加载失败,请重试')
} finally {
this.loading = false
}
},
// 重置列表
resetList() {
this.list = []
this.page = 1
this.noMore = false
this.getList()
},
// 模拟API请求
mockApi(page, pageSize) {
return new Promise(resolve => {
// 假设只有5页数据
const hasMore = page <= 5
const data = hasMore
? Array(pageSize).fill().map((_, i) => ({
id: (page - 1) * pageSize + i + 1,
title: `用户${(page - 1) * pageSize + i + 1}`,
desc: `这是第${page}页的第${i + 1}条数据,总第${(page - 1) * pageSize + i + 1}条`,
avatar: `https://randomuser.me/api/portraits/men/${((page - 1) * pageSize + i) % 100}.jpg`
}))
: []
resolve({ data, total: 50 })
})
},
// 辅助方法:延时
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
}
}
</script>
<style scoped>
.scroll-demo {
max-width: 360px;
margin: 0 auto;
padding: 10px;
}
.demo-title {
text-align: center;
margin-bottom: 15px;
font-size: 18px;
}
.list-container {
height: 70vh;
overflow-y: auto;
border: 1px solid #ebeef5;
border-radius: 4px;
background-color: #fff;
}
.list-item {
display: flex;
padding: 12px 15px;
border-bottom: 1px solid #f0f0f0;
}
.item-avatar {
width: 40px;
height: 40px;
margin-right: 12px;
}
.item-avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.item-content {
flex: 1;
}
.item-title {
font-size: 16px;
font-weight: 500;
margin-bottom: 4px;
}
.item-desc {
font-size: 13px;
color: #606266;
}
.loading-status, .no-more {
text-align: center;
padding: 15px;
color: #909399;
font-size: 14px;
}
.control-panel {
margin-top: 15px;
padding: 10px;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f5f7fa;
border-radius: 4px;
}
</style>
\ No newline at end of file
// 表格吸顶
import scroll from './scroll'
const install = function(Vue) {
Vue.directive('scroll', scroll)
}
if (window.Vue) {
window.scroll = scroll
Vue.use(install); // eslint-disable-line
}
scroll.install = install
export default scroll
/*
* 我想封装这样的下拉加载更多的功能指令
* 1. 当页面滚动到底部或者接近底部的时候 触发接口请求请求下一页的数据
* 2.触发的时候 引入 debounce 防抖函数
*/
import { throttle } from '@/utils/index';
const InfiniteScroll = {
name: 'infinite-scroll',
inserted(el, binding, vnode) {
const options = parseOptions(el, binding);
el.__infinite_scroll_options = options;
// 使用节流控制滚动事件触发频率
const scrollHandler = createScrollHandler(el, binding, vnode);
el.__infinite_scroll_handler = throttle(scrollHandler, 200);
// 绑定滚动事件
el.__infinite_scroll_container = bindEvents(el, el.__infinite_scroll_handler);
// 初始检查一次(处理内容不足一屏的情况)
setTimeout(() => {
el.__infinite_scroll_handler();
}, 50);
},
update(el, binding) {
// 更新配置
el.__infinite_scroll_options = parseOptions(el, binding);
},
unbind(el) {
// 解绑事件
if (el.__infinite_scroll_container && el.__infinite_scroll_handler) {
unbindEvents(el, el.__infinite_scroll_container, el.__infinite_scroll_handler);
}
// 清理引用
delete el.__infinite_scroll_container;
delete el.__infinite_scroll_handler;
delete el.__infinite_scroll_options;
}
};
// 解析配置选项
function parseOptions(el, binding) {
return {
// 距离底部多远触发加载,默认30px
distance: binding.arg ? parseInt(binding.arg) : 30,
// 是否禁用
disabled: binding.modifiers.disabled,
// 加载函数
loadMore: binding.value
};
}
// 创建滚动处理函数
function createScrollHandler(el, binding, vnode) {
return function() {
const options = el.__infinite_scroll_options;
// 检查是否禁用
if (options.disabled) return;
const scrollContainer = el.__infinite_scroll_container;
const isBottom = isScrollBottom(scrollContainer, el, options.distance);
if (isBottom) {
// 调用加载函数
options.loadMore();
}
};
}
// 绑定事件
function bindEvents(el, handler) {
const scrollContainer = getScrollContainer(el);
scrollContainer.addEventListener('scroll', handler);
// 移动端支持
if ('ontouchend' in document) {
scrollContainer.addEventListener('touchend', handler);
}
return scrollContainer;
}
// 解绑事件
function unbindEvents(el, container, handler) {
container.removeEventListener('scroll', handler);
if ('ontouchend' in document) {
container.removeEventListener('touchend', handler);
}
}
// 获取滚动容器
function getScrollContainer(el) {
let container = el;
while (container && container !== document.body) {
const { overflowY } = window.getComputedStyle(container);
if (['auto', 'scroll', 'overlay'].includes(overflowY)) {
return container;
}
container = container.parentNode;
}
// 默认返回window
return window;
}
// 判断是否滚动到底部
function isScrollBottom(scrollContainer, el, distance = 30) {
// window情况
if (scrollContainer === window) {
return (
Math.ceil(window.innerHeight + window.pageYOffset) >=
document.documentElement.scrollHeight - distance
);
}
// DOM元素情况
return (
Math.ceil(scrollContainer.scrollTop + scrollContainer.clientHeight) >=
scrollContainer.scrollHeight - distance
);
}
export default InfiniteScroll;
\ No newline at end of file
......@@ -20,7 +20,8 @@ import globalComponent from '@/components/register.js'
import loadmore from '@/directive/loadmore/index.js' // 加载更多
import clickagain from './directive/clickagain'
import permission from '@/directive/permission/index.js' // 权限判断指令
Vue.use(globalComponent).use(permission).use(clickagain).use(loadmore)
import scroll from '@/directive/scroll' // 下拉加载更多指令
Vue.use(globalComponent).use(permission).use(clickagain).use(loadmore).use(scroll)
// 导入 VConsole 清理工具
import '@/utils/vconsoleCleanup'
......
......@@ -16,6 +16,7 @@ const state = {
gameUserInfo:{},
send_game_log: null, // 转游发送渠道新增日志发送信息
chatUserInfo: {}, // 当前选中的用户的详情
viewLoading:false, // 查看用户详情的时候 加载状态
}
const mutations = {
......@@ -39,6 +40,9 @@ const mutations = {
},
set_send_game_log(state, data) {
state.send_game_log = data
},
set_viewLoading(state, data) {
state.viewLoading = data
}
}
......
......@@ -86,6 +86,22 @@ div:focus {
height: 1px;
background-color: #e4e7ed;
}
.el-tabs{
height:100%;
}
.el-tabs__content{
height: 100%;
}
.el-tab-pane{
height: 100%;
}
.el-tabs .el-tabs__header{
margin: 0;
// height: 60px;
}
.el-tabs__nav-next, .el-tabs__nav-prev{
line-height: 50px;
}
/* 统一下拉框高度 */
.el-select-dropdown__item {
......
......@@ -259,15 +259,15 @@ export default {
<style lang="scss" scoped>
.violationRecord {
width: 100%;
height: calc(100vh - 170px);
height: 100%;
background: #fff;
overflow-y: auto;
overflow-x: hidden;
.violationRecordContent {
width: 100%;
padding: 20px;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
height: auto;
}
.contentItem {
......
......@@ -302,7 +302,6 @@ export default {
background: #fff;
margin-left: 2px;
position: relative;
overflow: hidden;
.detailsTitle {
width: 100%;
padding: 0 vw(20);
......@@ -321,6 +320,8 @@ export default {
width: 100%;
height:100%;
padding: 20px 10px;
overflow: auto;
overflow-x: hidden;
.tabSelect{
width: 100%;
height: 60px;
......@@ -342,8 +343,7 @@ export default {
}
.list{
width: 100%;
height: calc(100% - 90px);
overflow: auto;
height: auto;
}
.contentItem{
position: relative;
......@@ -456,9 +456,6 @@ export default {
}
.orderDetailsScroll{
width: 100%;
height: calc(100% - 20px);
overflow: auto;
overflow-x: hidden;
.orderDetailsScrollContent{
margin-bottom: 50px;
}
......
......@@ -74,7 +74,8 @@ export default {
...mapMutations('game', [
'set_accountSelect',
'set_chatUserInfo',
'set_gameUserInfo'
'set_gameUserInfo',
'set_viewLoading'
]),
...mapActions('game', ['gameBindUser']),
handleChange(value) {
......@@ -89,17 +90,19 @@ export default {
},
gameMemberView(item) {
if (this.accountSelect && this.accountSelect !== '') {
this.set_viewLoading(true)
const data = { member_id: this.accountSelect, need_channel: 1, need_roleInfo: 1, need_banned: 1 }
memberView(data).then((res) => {
this.set_viewLoading(false)
if (res.status_code === 1) {
this.set_gameUserInfo(res.data)
} else {
this.set_gameUserInfo({})
}
}, (err) => {
this.set_viewLoading(false)
this.set_gameUserInfo({})
})
}
},
logout(){
......
......@@ -95,7 +95,7 @@ import Clipboard from 'clipboard'
<style lang="scss" scoped>
.wx-gift-container {
height: calc(100vh - 230px);
height: 100%;
width: 100%;
background-color: #fff;
......
<template>
<div class="detailsRefund columnFlex">
<div class="content refundContent">
<div class="refundContent">
<div class="filter-container">
<el-form class="filter-form" label-position="top" label-width="auto" :class="{ 'collapsed-form': isCollapsed }">
<div class="filter-header">
......@@ -501,11 +501,11 @@
}
}
.content {
.refundContent {
width: 100%;
height: 100%;
padding-top: 20px;
overflow: auto;
overflow-x: hidden;
.tabSelect {
width: 100%;
height: 60px;
......@@ -643,9 +643,7 @@
}
.detailsRefundScroll {
width: 100%;
height: calc(100% - 250px);
overflow: auto;
overflow-x: hidden;
height: auto;
padding-right: 10px;
border-radius: 5px;
}
......@@ -806,9 +804,6 @@
transition: all 0.3s;
.filter-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px dashed #EBEEF5;
......@@ -842,7 +837,7 @@
.el-form-item {
margin-bottom: 15px;
width: 48%;
width: 100%;
margin-right: 2%;
display: inline-block;
vertical-align: top;
......
......@@ -84,7 +84,7 @@
v-infinite-scroll="paperScroll"
:infinite-scroll-disabled="!isMoreRecord"
:infinite-scroll-immediate="false"
class="mailListScroll"
class="approvalScroll"
>
<!-- 举报申请 -->
<div class="scrollMain" v-if="reportList.length > 0">
......@@ -512,9 +512,9 @@
<style lang="scss" scoped>
.approval-role-list {
width: 100%;
height: calc(100vh - 200px);
overflow: auto;
height: 100%;
padding-top: 10px;
overflow: auto;
.taskForm {
::v-deep .el-form-item {
margin-bottom: 10px;
......@@ -567,12 +567,9 @@
}
}
.mailListScroll {
.approvalScroll {
width: 100%;
overflow-y: auto;
overflow-x: hidden;
display: flex;
flex: 1;
height: auto;
.scrollMain {
width: 100%;
height: auto;
......
......@@ -517,8 +517,8 @@
.reportList {
width: 100%;
height: 100%;
overflow: auto;
overflow-y: auto;
overflow-x: hidden;
.taskForm {
::v-deep .el-form-item {
margin-bottom: 10px;
......@@ -573,15 +573,11 @@
.mailListScroll {
width: 100%;
height: calc(100% - 250px);
overflow-y: auto;
overflow-x: hidden;
height: auto;
.scrollMain {
width: 100%;
height: auto;
margin-bottom: 40px;
.reportContent {
width: calc(100% - 10px);
}
......
......@@ -196,6 +196,7 @@
height: 100%;
background: #fff;
margin-left: 2px;
overflow: auto;
.detailsTitle {
width: 100%;
padding: 0 vw(20);
......@@ -212,7 +213,7 @@
}
.detailsContent {
width: 100%;
height: calc(100vh - 186px);
height: 100%;
overflow: auto;
.textInfo {
font-family: PingFangSC-Regular, PingFang SC;
......
......@@ -2,10 +2,10 @@
<div class="gift-tab-container-apply-gift">
<el-tabs v-model="activeTab">
<el-tab-pane label="充值礼包" name="email">
<email-gift></email-gift>
<email-gift v-if="activeTab == 'email'"></email-gift>
</el-tab-pane>
<el-tab-pane label="企微礼包" name="wx">
<wx-gift></wx-gift>
<wx-gift v-if="activeTab == 'wx'"></wx-gift>
</el-tab-pane>
</el-tabs>
</div>
......@@ -50,6 +50,10 @@ export default {
padding-top: 10px;
background: #fff;
::v-deep .el-tabs__content{
height: calc(100% - 55px);
}
::v-deep .el-tabs {
.el-tabs__header {
margin: 0;
......@@ -67,6 +71,7 @@ export default {
}
}
.el-tabs__active-bar {
background-color: #3491FA;
height: 3px;
......
......@@ -126,13 +126,8 @@
<!-- 右侧操作区域 -->
<div class="contact-actions">
<!-- 待绑定标签 -->
<div v-if="item.red_tip == 1" class="unbind-tag">
待绑定
</div>
<!-- 发起会话按钮 -->
<el-button
v-else
type="primary"
size="small"
class="chat-btn"
......
......@@ -3,7 +3,7 @@
<div
class="userDetailsPanel columnFlex"
>
<div class="content">
<div class="content" v-loading="viewLoading">
<div v-if="chatUserDetails.is_phishing_account==1" class="warnText">
<p>高风险玩家,请立即通知组长!!!!</p>
<p>①千万不能推转游!!不要发送违禁词汇!!不要发送礼包和告知任何礼包信息!!</p>
......@@ -240,7 +240,8 @@ import watchMember from '@/mixins/watchMember'
...mapState('game', [
'accountSelect',
'gameUserInfo',
'bindGameUserList'
'bindGameUserList',
'viewLoading'
]),
...mapState('user', ['cser_info','cser_id','cser_name'])
},
......
......@@ -80,7 +80,6 @@ export default {
::v-deep .el-tabs__content {
height: calc(100% - 55px);
overflow-y: auto;
}
}
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论