Compare commits

...

13 Commits

Author SHA1 Message Date
b58f25945e 修改部门选择规则
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-03 15:22:53 +08:00
bdfd84bd35 修改部分选择人员的规则与交互 2025-02-18 19:51:23 +08:00
a0578c57ad 解决群管理员列表【我】的判断规则不适配新版的问题;新增管理员选择页面搜索功能 2025-02-14 19:52:01 +08:00
e9bd2bf6cd 修改部门群管理员岗位新增修改规则,增加部门岗位对应关系 2025-02-13 08:49:20 +08:00
2c063f3d4f 重构部分聊天详情页面;重构部分管理员选择页面 2025-02-11 11:45:19 +08:00
5c55411aa3 重构部分聊天选择页面 2025-02-08 17:09:57 +08:00
df07c953bf 重构选择群类型页面部分样式;将选择部门和选择群成员合并到一起;去除选择管理员页面,复用设置管理员页面;调整按部门选择人员样式和逻辑;调整创建群聊部分规则 2025-02-07 17:00:57 +08:00
dafe65bb72 修改选择部门和新增群成员页面交互与样式;新增通讯录页面复用部分适配 2025-02-06 17:01:28 +08:00
78ca543946 新增通讯录页面;新增发起单聊功能;新增聊天会话左滑删除并同时删除localStorage中的记录 2025-02-05 16:22:32 +08:00
2e3b0b994b 新增自定义头像通用组件;进行发起群聊、选择部门页面部分重构 2025-01-24 17:01:50 +08:00
8704691821 更新zpaging组件版本至最新;新增自定义导航栏组件 2025-01-24 11:18:26 +08:00
4f57419c5d 实现普通群项目群的拉人、踢人功能;优化人员选择功能界面 2025-01-23 16:45:49 +08:00
489fb71be5 实现普通群创建和项目群创建;实现群头像修改功能;实现项目群和普通群的管理员设置、移除、查看 2025-01-22 16:37:28 +08:00
78 changed files with 6816 additions and 2169 deletions

4
env/.env.test vendored
View File

@ -6,8 +6,8 @@ VITE_SHOW_CONSOLE = true
VITE_SHOW_SOURCEMAP = true
# baseUrl
# VITE_BASEURL = 'https://warehouse.szjixun.cn/oa_backend'
VITE_BASEURL = 'http://172.16.100.93:9503'
VITE_BASEURL = 'http://172.16.100.93:8503'
#VITE_SOCKET_API
VITE_SOCKET_API = 'ws://172.16.100.93:9504'
VITE_SOCKET_API = 'ws://172.16.100.93:8504'
# EPRAPI baseUrl
VITE_EPR_BASEURL = 'http://114.218.158.24:9020'

View File

@ -135,6 +135,15 @@ export const ServeRemoveRecords = (data) => {
})
}
//清空聊天记录
export const ServeEmptyMessage = (data) => {
return request({
url: '/api/v1/talk/message/empty',
method: 'POST',
data,
})
}
// 收藏表情包服务接口
export const ServeCollectEmoticon = (data) => {
return request({

View File

@ -1,29 +1,94 @@
<script setup>
import TmImage from "@/uni_modules/tmui/components/tm-image/tm-image.vue";
import {useAuth} from "@/store/auth";
import { useClockIn } from "@/store/clockIn/index.js";
const {userInfo}=useAuth()
const {workingTimeInfoData,actionTypeData} = useClockIn()
</script>
<template>
<div class="flex-shrink-0 pl-[16rpx] pr-[40rpx] flex items-center rounded-[8rpx] w-[686rpx] h-[154rpx] bg-white">
<div class="rounded-full overflow-hidden w-[96rpx] h-[96rpx]">
<tm-image preview :width="96" :height="96" :src="userInfo.Avatar"></tm-image>
</div>
<div class="ml-[20rpx]">
<div class="flex items-center">
<div class="text-[32rpx] text-black">{{ userInfo.NickName }}</div>
<div class="mx-[14rpx] h-[30rpx] w-[1rpx] bg-[#F7F7F7]"></div>
<div class="w-[40rpx] h-[40rpx]">
<img v-if="actionTypeData.isWorkDay ===1" class="w-[40rpx] h-[40rpx]" src="@/static/image/clockIn/zu3275@3x.png" alt="">
<img v-else class="w-[40rpx] h-[40rpx]" src="@/static/image/clockIn/rest3275@2x.png" alt="">
</div>
</div>
<div class="mt-[5rpx] flex">
<div class="text-[24rpx] text-[#999999]">{{ workingTimeInfoData.WorkTimeTemplateName }}</div>
<div class="text-[#46299D] text-[24rpx]">考勤规则</div>
</div>
</div>
<slot name="right"></slot>
<div class="avatar-module" :style="customStyle">
<img :src="avatar" v-if="avatar" />
<span v-else :style="customTextStyle">{{ text_avatar }}</span>
</div>
</template>
<script setup>
//
import groupNormal from '@/static/image/chatList/groupNormal.png'
import groupDepartment from '@/static/image/chatList/groupDepartment.png'
import groupProject from '@/static/image/chatList/groupProject.png'
import groupCompany from '@/static/image/chatList/groupCompany.png'
import { computed, defineProps } from 'vue'
const props = defineProps({
mode: {
//1=2=
type: Number,
default: 0,
},
avatar: {
//
type: String,
default: '',
},
userName: {
//
type: String,
default: '',
},
groupType: {
//1=2=3=4=/
type: Number,
default: 0,
},
customStyle: {
//
type: Object,
default() {
return {}
},
},
customTextStyle: {
//
type: Object,
default() {
return {}
},
},
})
//
const avatar = computed(() => {
let avatar_img = props?.avatar
if (!avatar_img) {
if (props?.mode === 1) {
} else if (props?.mode === 2) {
if (props?.groupType === 1) {
avatar_img = groupNormal
} else if (props?.groupType === 2) {
avatar_img = groupDepartment
} else if (props?.groupType === 3) {
avatar_img = groupProject
} else if (props?.groupType === 4) {
avatar_img = groupCompany
}
}
}
return avatar_img
})
//
const text_avatar = computed(() => {
return props?.userName.length >= 2
? props?.userName.slice(-2)
: props?.userName
})
</script>
<style lang="scss" scoped>
.avatar-module {
border-radius: 50%;
overflow: hidden;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background: linear-gradient(to right, #674bbc, #46299d);
flex-shrink: 0;
img {
width: 100%;
height: 100%;
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div @click="onCheck" >
<div @click.stop="onCheck" >
<tm-image :width="size" :height="size" :src="imageSrc"></tm-image>
</div>
</template>

View File

@ -9,7 +9,13 @@
<wd-button custom-class="custom-sub-btn-class" v-if="props.subBtnText">
{{ props.subBtnText }}
</wd-button>
<wd-button custom-class="custom-btn-class" @click="clickBtn">
<wd-button
custom-class="custom-btn-class"
@click="clickBtn"
:disabled="props?.disabled"
:class="[props?.disabled ? 'custom-btn-class-disabled' : '']"
:plain="props?.plain"
>
{{ props.btnText }}
</wd-button>
</div>
@ -23,6 +29,8 @@ const props = defineProps({
isBottom: false, //
btnText: '', //
subBtnText: '', //
disabled: false, //
plain: false, //
})
//
@ -38,28 +46,33 @@ const clickBtn = () => {
justify-content: center;
.custom-sub-btn-class {
background-color: #eee9f8;
padding: 18rpx 185rpx;
padding: 18rpx 0;
border-radius: 8rpx;
box-shadow: 0 6px 12px 2px rgba(188, 188, 188, 0.08);
font-size: 28rpx;
font-weight: 500;
line-height: 40rpx;
color: $theme-primary;
width: 426rpx;
height: 76rpx;
}
.custom-btn-class {
background-color: $theme-primary;
padding: 18rpx 185rpx;
padding: 18rpx 0;
border-radius: 8rpx;
box-shadow: 0 6px 12px 2px rgba(188, 188, 188, 0.08);
font-size: 28rpx;
font-weight: 500;
line-height: 40rpx;
width: 426rpx;
height: 76rpx;
}
}
.custom-btn-bottom {
width: 100%;
background-color: #fff;
padding: 14rpx 0 72rpx;
box-shadow: 0 1px 0 2px rgba(231, 231, 231, 1);
}
.apposition-btn-style {
padding: 14rpx 30rpx 72rpx;
@ -71,4 +84,8 @@ const clickBtn = () => {
padding: 18rpx 124rpx;
}
}
.custom-btn-class-disabled {
background-color: #e6e6e6 !important;
color: #bebebe !important;
}
</style>

View File

@ -0,0 +1,50 @@
<template>
<tm-navbar
:hideBack="props.hideBack"
hideHome
:title="props.title"
:shadow="props.shadowNum"
:fontSize="34"
>
<template #left>
<slot name="left"></slot>
</template>
<template #subTitle>
<slot name="subTitle"></slot>
</template>
<template #right>
<slot name="right"></slot>
</template>
</tm-navbar>
</template>
<script setup>
import tmNavbar from '@/uni_modules/tmui/components/tm-navbar/tm-navbar.vue'
import { defineProps } from 'vue'
const props = defineProps({
title: {
//
type: String,
default: '',
},
hideBack: {
//
type: Boolean,
default: false,
},
shadowNum: {
//
type: Number,
default: 1,
},
})
</script>
<style scoped lang="scss">
::v-deep .text-view {
text {
font-weight: 500;
line-height: 48rpx;
}
}
</style>

View File

@ -1,79 +1,114 @@
<script setup>
import {ref,nextTick} from 'vue'
import WdPopup from "@/uni_modules/wot-design-uni/components/wd-popup/wd-popup.vue";
import TmButton from "@/uni_modules/tmui/components/tm-button/tm-button.vue";
import {language} from "@/uni_modules/tmui/tool/lib/language"
const confirmState=ref(false)
const cancel=ref(true)
let onConfirm=null
let onCancel=null
const confirm=ref(true)
const contentText=ref('')
const imageRef=ref('')
const confirmLabel=ref('确定')
const cancelLabel=ref('取消')
const confirmC=ref('#46299D')
const cancelC=ref('#1A1A1A')
const sendCancel=()=>{
confirmState.value=false
if (typeof onCancel==='function'){
import { ref, nextTick } from 'vue'
import WdPopup from '@/uni_modules/wot-design-uni/components/wd-popup/wd-popup.vue'
import TmButton from '@/uni_modules/tmui/components/tm-button/tm-button.vue'
import { language } from '@/uni_modules/tmui/tool/lib/language'
const confirmState = ref(false)
const cancel = ref(true)
let onConfirm = null
let onCancel = null
const confirm = ref(true)
const contentText = ref('')
const subContentText = ref('')
const subContentC = ref('#000')
const imageRef = ref('')
const confirmLabel = ref('确定')
const cancelLabel = ref('取消')
const confirmC = ref('#46299D')
const cancelC = ref('#1A1A1A')
const sendCancel = () => {
confirmState.value = false
if (typeof onCancel === 'function') {
onCancel()
}
}
const sendConfirm=()=>{
confirmState.value=false
if (typeof onConfirm==='function'){
const sendConfirm = () => {
confirmState.value = false
if (typeof onConfirm === 'function') {
onConfirm()
}
}
const showConfirm=({content,image,onConfirm:confirm,onCancel:cancel,confirmText,cancelText,confirmColor,cancelColor})=>{
confirmState.value=true
contentText.value=content
imageRef.value = image?image:''
onConfirm=confirm
onCancel=cancel
const showConfirm = ({
content,
image,
onConfirm: confirm,
onCancel: cancel,
confirmText,
cancelText,
confirmColor,
cancelColor,
subContent,
subContentColor,
}) => {
confirmState.value = true
contentText.value = content
imageRef.value = image ? image : ''
onConfirm = confirm
onCancel = cancel
confirmLabel.value = confirmText || confirmLabel.value
cancelLabel.value = cancelText || cancelLabel.value
confirmC.value = confirmColor || confirmC.value
cancelC.value = cancelColor || cancelC.value
subContentText.value = subContent || subContentText.value
subContentC.value = subContentColor || subContentC.value
}
defineExpose({
showConfirm
showConfirm,
})
</script>
<template>
<wd-popup custom-style="border-radius: 16rpx;" modal-style="background-color: #000000;opacity: 0.6;" v-model="confirmState">
<wd-popup
custom-style="border-radius: 16rpx;"
modal-style="background-color: #000000;opacity: 0.6;"
v-model="confirmState"
>
<div class="flex flex-col w-[640rpx]">
<div v-if="imageRef===''" class="flex justify-center items-center h-[288rpx] text-[32rpx] font-bold text-[#1A1A1A]">
{{contentText}}
<div
v-if="imageRef === ''"
class="flex flex-col justify-center items-center h-[288rpx] text-[#1A1A1A] popup-content"
>
<span class="text-[32rpx] font-bold">{{ contentText }}</span>
<span
class="text-[28rpx] font-regular"
v-if="subContentText"
:style="{ color: subContentC }"
>
{{ subContentText }}
</span>
</div>
<div v-else class="flex flex-col items-center h-[456rpx] text-[32rpx] font-bold text-[#1A1A1A]" >
<div class="wrap1 mt-[32rpx] mb-[44rpx]" >
<img :src="imageRef" alt="">
<div
v-else
class="flex flex-col items-center h-[456rpx] text-[32rpx] font-bold text-[#1A1A1A]"
>
<div class="wrap1 mt-[32rpx] mb-[44rpx]">
<img :src="imageRef" alt="" />
</div>
<div class="mb-[56rpx]" > {{contentText}} </div>
<div class="mb-[56rpx]">{{ contentText }}</div>
</div>
<div class="flex flex-grow border-t-solid border-[#E7E7E7] border-1rpx text-[32rpx]">
<div
class="flex flex-grow border-t-solid border-[#E7E7E7] border-1rpx text-[32rpx]"
>
<div class="flex justify-center items-center text-[#1A1A1A]">
<tm-button
@click="sendCancel"
:width="319"
@touchstart="cancel=false"
@touchend="cancel=true"
@touchstart="cancel = false"
@touchend="cancel = true"
:fontSize="32"
:height="112"
:margin="[0]"
:font-color="cancelC"
:transprent="cancel"
text
:label="cancelLabel"></tm-button>
:label="cancelLabel"
></tm-button>
</div>
<div class="h-[112rpx] w-[1rpx] bg-[#E7E7E7]"></div>
<div class="flex justify-center items-center text-[#CF3050]">
<tm-button
@click="sendConfirm"
@touchstart="confirm=false"
@touchend="confirm=true"
@touchstart="confirm = false"
@touchend="confirm = true"
:width="319"
:fontSize="32"
:transprent="confirm"
@ -81,17 +116,23 @@ defineExpose({
:margin="[0]"
:font-color="confirmC"
text
:label="confirmLabel"></tm-button>
:label="confirmLabel"
></tm-button>
</div>
</div>
</div>
</wd-popup>
</template>
<style scoped lang="scss">
.popup-content {
span {
line-height: 44rpx;
}
}
.wrap1 {
img {
width: 381.59rpx;
height: 280.14rpx;
}
}
}
</style>

View File

@ -1,11 +0,0 @@
<script setup>
import tmNavbar from '@/uni_modules/tmui/components/tm-navbar/tm-navbar.vue';
import {useStatus} from "@/store/status"
const {currentNavbar} = useStatus()
</script>
<template>
<tm-navbar :hideBack="false" hideHome :title="currentNavbar.title"/>
</template>
<style scoped lang="scss">
</style>

View File

@ -12,7 +12,7 @@ import { reactive, nextTick, computed, h, inject } from 'vue'
// IdCard
// } from '@icon-park/vue-next'
import { ServeTopTalkList, ServeDeleteTalkList, ServeSetNotDisturb } from '@/api/chat'
import { useDialogueStore, useTalkStore } from '@/store'
import { useDialogueStore, useTalkStore, useDialogueListStore } from '@/store'
import { ServeSecedeGroup } from '@/api/group'
// import { ServeDeleteContact, ServeEditContactRemark } from '@/api/contact'
// import { NInput } from 'naive-ui'
@ -28,6 +28,7 @@ export function useSessionMenu() {
const dialogueStore = useDialogueStore()
const talkStore = useTalkStore()
const dialogueListStore = useDialogueListStore()
const user = inject('$user')
@ -105,6 +106,7 @@ export function useSessionMenu() {
const onDeleteTalk = (index_name = '') => {
talkStore.delItem(index_name)
dialogueListStore.delDialogueStorage(index_name)
index_name === indexName.value && dialogueStore.$reset()
}
@ -264,5 +266,5 @@ export function useSessionMenu() {
evnets[key] && evnets[key](dropdown.item)
}
return { dropdown, onCloseContextMenu, onContextMenuTalkHandle, onToTopTalk }
return { dropdown, onCloseContextMenu, onContextMenuTalkHandle, onToTopTalk, onRemoveTalk }
}

View File

@ -1,52 +1,55 @@
import customNavbar from '@/components/custom-navbar/index'
import { createSSRApp } from 'vue'
import App from './App.vue'
import dayjs from "dayjs";
import dayjs from 'dayjs'
import 'virtual:uno.css'
import VConsole from "vconsole";
import VConsole from 'vconsole'
import '@/utils/uni.webview.js'
import tmui from "@/uni_modules/tmui"
import {config} from "@/config/tmui/index.js";
import 'dayjs/locale/zh-cn';
import tmui from '@/uni_modules/tmui'
import { config } from '@/config/tmui/index.js'
import 'dayjs/locale/zh-cn'
import xLoaderror from '@/components/x-loaderror/index.vue'
import { vLoading } from "@/components/x-loading/index.js"
import { vLoading } from '@/components/x-loading/index.js'
import messagePopup from '@/components/x-message/useMessagePopup'
import pageAnimation from '@/components/page-animation/index.vue'
import * as plugins from './plugins'
const {showMessage}=messagePopup()
const { showMessage } = messagePopup()
dayjs.locale('zh-cn')
if (import.meta.env.VITE_SHOW_CONSOLE){
if (import.meta.env.VITE_SHOW_CONSOLE) {
new VConsole()
}
export function createApp() {
const app = createSSRApp(App)
plugins.setPinia(app)
plugins.setComponents(app)
app.use(tmui,{...config})
app.directive("loading", vLoading)
app.use(tmui, { ...config })
app.directive('loading', vLoading)
app.mixin(pageAnimation)
app.component('x-loaderror',xLoaderror)
app.component('customNavbar', customNavbar)
app.component('x-loaderror', xLoaderror)
app.directive('no-space', {
mounted(el) {
el.addEventListener('input', (e) => {
const originalValue = e.target.value;
const newValue = originalValue.replace(/\s/g, '');
const originalValue = e.target.value
const newValue = originalValue.replace(/\s/g, '')
if (originalValue !== newValue) {
e.target.value = newValue;
e.target.dispatchEvent(new Event('input'));
}
});
e.target.value = newValue
e.target.dispatchEvent(new Event('input'))
}
})
},
})
window.message = ['success', 'error', 'warning'].reduce((acc, type) => {
acc[type] = (message) => {
if (typeof message === 'string') {
showMessage({ type, message });
showMessage({ type, message })
} else if (typeof message === 'object') {
showMessage({ type, ...message });
showMessage({ type, ...message })
}
};
return acc;
}, {});
}
return acc
}, {})
return {
app,
}

View File

@ -38,22 +38,6 @@
"enablePullDownRefresh": false
}
},
{
"path": "pages/chooseDeps/index",
"type": "page",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/chooseMembers/index",
"type": "page",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh":false
}
},
{
"path": "pages/chooseGroupAdmin/index",
"type": "page",
@ -186,6 +170,14 @@
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/chooseByDeps/index",
"type": "page",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
}
],
"globalStyle": {

View File

@ -19,12 +19,12 @@
"
>
<div class="group-member-avatar">
<img v-if="memberItem.avatar" :src="memberItem.avatar" />
<span v-if="!memberItem.avatar" class="text-[24rpx] font-bold">
<img v-if="memberItem?.avatar" :src="memberItem?.avatar" />
<span v-if="!memberItem?.avatar" class="text-[24rpx] font-bold">
{{
memberItem.nickname.length >= 2
? memberItem.nickname.slice(-2)
: memberItem.nickname
(memberItem?.nickname || memberItem?.nickName)?.length >= 2
? (memberItem?.nickname || memberItem?.nickName).slice(-2)
: memberItem?.nickname || memberItem?.nickName
}}
</span>
</div>
@ -38,12 +38,19 @@
</div>
<div class="group-member-name">
<span class="text-[24rpx] font-regular">
{{ memberItem.nickname }}
{{ memberItem.nickname || memberItem.nickName }}
</span>
</div>
</div>
</div>
<div class="group-member-list-each">
<div
class="group-member-list-each"
@click="groupAddMembers"
v-if="
(props?.groupType == 1 || props?.groupType == 3) &&
!props?.hideAddRemoveBtns
"
>
<div class="group-member-each">
<div class="group-member-avatar" :style="{ background: 'unset' }">
<img src="/src/static/image/chatSettings/add-member.png" />
@ -53,7 +60,15 @@
</div>
</div>
</div>
<div class="group-member-list-each" v-if="props?.is_manager">
<div
class="group-member-list-each"
@click="groupRemoveMembers"
v-if="
props?.is_manager &&
(props?.groupType == 1 || props?.groupType == 3) &&
!props?.hideAddRemoveBtns
"
>
<div class="group-member-each">
<div class="group-member-avatar" :style="{ background: 'unset' }">
<img src="/src/static/image/chatSettings/remove-member.png" />
@ -71,14 +86,32 @@ const props = defineProps({
memberList: Array, //
memberListsLimit: Number, //
is_manager: Boolean, //
groupType: Number, //
hideAddRemoveBtns: Boolean, //
})
//
const toUserDetailPage = (userItem) => {
console.log(userItem.erp_user_id)
console.log(userItem)
uni.navigateTo({
url:
'/pages/dialog/dialogDetail/userDetail?erpUserId=' + userItem.erp_user_id,
'/pages/dialog/dialogDetail/userDetail?erpUserId=' +
(userItem.erp_user_id || userItem.ID),
})
}
//
const groupAddMembers = () => {
uni.navigateTo({
url: '/pages/chooseByDeps/index?chooseMode=2',
})
}
//
const groupRemoveMembers = () => {
uni.navigateTo({
url:
'/pages/chatSettings/groupManage/selectMembers?manageType=removeMembers',
})
}
</script>

View File

@ -7,6 +7,10 @@
? 'select-member-item-card'
: 'select-member-item-list'
"
:style="{
padding:
props?.manageType === 'removeMembers' ? '18rpx 62rpx 18rpx 34rpx' : '',
}"
>
<div class="member-info">
<slot name="left"></slot>
@ -39,12 +43,21 @@
{{ $t('silence.tag.hasDone') }}
</span>
</div>
<div
class="is-mine-tag"
v-if="
props?.memberItem?.is_mine && props?.manageType === 'removeMembers'
"
>
<img src="/src/static/image/chatSettings/is-mine.png" />
</div>
<div
class="is-admin-tag"
v-if="
(props?.memberItem?.leader === 1 ||
props?.memberItem?.leader === 2) &&
props?.manageType === 'admin'
(props?.manageType === 'admin' ||
props?.manageType === 'removeMembers')
"
>
<span class="text-[28rpx] font-regular">
@ -135,16 +148,31 @@ const clickItem = () => {
}
}
.select-member-tags {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
.done-silence-tag {
span {
color: #b4b4b4;
line-height: 40rpx;
}
}
.is-mine-tag {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
img {
width: 52rpx;
height: 52rpx;
}
}
.is-admin-tag {
padding: 6rpx 12rpx;
border: 2rpx solid #b4b4b4;
border-radius: 8rpx;
margin: 0 0 0 20rpx;
span {
color: #b4b4b4;
line-height: 40rpx;

View File

@ -3,51 +3,114 @@
<div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
<div class="navBar-title flex flex-col items-center justify-center">
<span class="text-[34rpx] font-medium">
{{ $t('chat.settings.editAvatar') }}
</span>
</div>
</tm-navbar>
<customNavbar :title="$t('chat.settings.editAvatar')"></customNavbar>
</template>
<div class="edit-group-info">
<div class="group-avatar">
<img :src="state.groupAvatar" />
<avatarModule
:mode="2"
:avatar="groupParams?.groupInfo?.avatar"
:groupType="groupParams?.groupInfo?.group_type"
:customStyle="{
width: '460rpx',
height: '460rpx',
borderRadius: '0',
}"
></avatarModule>
</div>
</div>
<customBtn :btnText="$t('button.text.edit')"></customBtn>
<customBtn
:btnText="$t('button.text.edit')"
@click="editAvatar"
></customBtn>
</ZPaging>
</div>
</div>
</template>
<script setup>
import avatarModule from '@/components/avatar-module/index.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue'
import { ServeEditGroup } from '@/api/group/index.js'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { onLoad } from '@dcloudio/uni-app'
import { reactive } from 'vue'
import { reactive, computed, watch } from 'vue'
import { useGroupStore, useDialogueStore, useDialogueListStore } from '@/store'
const state = reactive({
pageTitle: '', //
groupAvatar: '', //
groupName: '', //
import { uploadImg } from '@/api/chat'
import { uniqueId } from '@/utils'
const groupStore = useGroupStore()
const groupParams = reactive({
groupInfo: computed(() => groupStore.groupInfo),
})
const dialogueStore = useDialogueStore()
const dialogueParams = reactive({
receiver_id: computed(() => dialogueStore.talk.receiver_id),
})
const onProgressFn = (progress, id) => {
console.log((progress.loaded / progress.total) * 100, 'progress')
useDialogueListStore().updateUploadProgress(
id,
(progress.loaded / progress.total) * 100,
)
}
onLoad((options) => {
console.log(options)
if (options.groupAvatar) {
state.groupAvatar = options.groupAvatar
}
})
//
const clearGroupNameInput = () => {
state.groupName = ''
}
//
const confirmEdit = () => {
console.log(state.groupName)
//
const editAvatar = () => {
uni.chooseImage({
sourceType: ['album'],
count: 1,
success: async (res) => {
console.log(res, 'res')
res.tempFiles.forEach(async (file) => {
console.log(file)
let image = new Image()
image.src = URL.createObjectURL(file)
image.onload = () => {
const form = new FormData()
form.append('file', file)
form.append('source', 'fonchain-chat')
form.append('urlParam', `width=${image.width}&height=${image.height}`)
let randomId = uniqueId()
uploadImg(form, (e) => onProgressFn(e, randomId)).then(
({ status, data, msg }) => {
console.log(status, data, msg)
if (status == 0) {
let avatar = data.ori_url
let params = {
group_id: dialogueParams.receiver_id,
group_name: groupParams.groupInfo.group_name,
avatar: avatar,
}
console.log(params)
const resp = ServeEditGroup(params)
resp.then(({ code }) => {
if (code == 200) {
groupStore.updateGroupInfo({
avatar: data.ori_url,
})
// uni.navigateBack({
// delta: 1,
// })
} else {
}
})
resp.catch(() => {})
} else {
}
},
)
}
})
},
})
}
</script>
<style scoped lang="scss">
@ -56,7 +119,7 @@ const confirmEdit = () => {
background-image: url('@/static/image/mine/background.png');
background-size: cover;
background-repeat: no-repeat;
background-position:center bottom;
background-position: center bottom;
}
.edit-group-info {
@ -70,10 +133,6 @@ const confirmEdit = () => {
flex-direction: row;
align-items: center;
justify-content: center;
img {
width: 460rpx;
height: 460rpx;
}
}
}
</style>

View File

@ -3,17 +3,21 @@
<div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
<div class="navBar-title flex flex-col items-center justify-center">
<span class="text-[34rpx] font-medium">
{{ $t('chat.settings.editGroupName') }}
</span>
</div>
</tm-navbar>
<customNavbar
:title="$t('chat.settings.editGroupName')"
></customNavbar>
</template>
<div class="edit-group-info">
<div class="group-avatar">
<img :src="state.groupAvatar" />
<avatarModule
:mode="2"
:avatar="groupParams?.groupInfo?.avatar"
:groupType="groupParams?.groupInfo?.group_type"
:customStyle="{
width: '192rpx',
height: '192rpx',
}"
></avatarModule>
</div>
<div class="group-name">
<span class="text-[28rpx] font-medium">
@ -46,6 +50,7 @@
</div>
</template>
<script setup>
import avatarModule from '@/components/avatar-module/index.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue'
import { ServeEditGroup } from '@/api/group/index.js'
import { useGroupStore, useDialogueStore } from '@/store'
@ -65,15 +70,11 @@ const dialogueParams = reactive({
const state = reactive({
pageTitle: '', //
groupAvatar: '', //
groupName: '', //
})
onLoad((options) => {
console.log(options)
if (options.groupAvatar) {
state.groupAvatar = options.groupAvatar
}
state.groupName = groupParams.groupInfo.group_name
})
@ -91,6 +92,7 @@ const confirmEdit = () => {
let params = {
group_id: dialogueParams.receiver_id,
group_name: state.groupName,
avatar: groupParams.groupInfo.avatar,
}
console.log(params)
const resp = ServeEditGroup(params)
@ -125,15 +127,10 @@ const confirmEdit = () => {
.edit-group-info {
.group-avatar {
padding: 250rpx 0 88rpx;
border-radius: 50%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
img {
width: 192rpx;
height: 192rpx;
}
}
.group-name {
padding: 0 32rpx;

View File

@ -3,13 +3,7 @@
<div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
<div class="navBar-title flex flex-col items-center justify-center">
<span class="text-[34rpx] font-medium">
{{ $t('chat.settings.groupAdmin') }}
</span>
</div>
</tm-navbar>
<customNavbar :title="$t('chat.settings.groupAdmin')"></customNavbar>
</template>
<div class="manage-group-admin">
<span class="manage-group-admin-title text-[28rpx] font-regular">
@ -23,10 +17,32 @@
>
<div
class="group-admin-list-each"
:style="{
padding:
groupParams?.groupInfo?.group_type == 1 ||
groupParams?.groupInfo?.group_type == 3
? '18rpx 26rpx 18rpx 14rpx'
: '',
}"
v-for="(item, index) in state?.groupAdminList"
:key="index"
>
<span>{{ item.deptPos }}</span>
<selectMemberItem
v-if="
groupParams?.groupInfo?.group_type == 1 ||
groupParams?.groupInfo?.group_type == 3
"
:groupType="groupParams.groupInfo.group_type"
:memberItem="item"
></selectMemberItem>
<span
v-if="
groupParams?.groupInfo?.group_type == 2 ||
groupParams?.groupInfo?.group_type == 4
"
>
{{ item.deptPos }}
</span>
<div class="group-admin-list-each-btns">
<img
v-if="item.is_mine"
@ -62,11 +78,15 @@
</div>
</template>
<script setup>
import selectMemberItem from '../components/select-member-item.vue'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { onLoad } from '@dcloudio/uni-app'
import { computed, onMounted, reactive, watch } from 'vue'
import { ServeEditGroupAdmin } from '@/api/group/index.js'
import {
ServeEditGroupAdmin,
ServeGroupAssignAdmin,
} from '@/api/group/index.js'
import { useGroupStore, useDialogueStore } from '@/store'
import { useAuth } from '@/store/auth'
@ -90,7 +110,7 @@ const state = reactive({
})
watch(
() => groupParams?.groupInfo,
[() => groupParams?.groupInfo, , () => dialogueParams?.adminList],
(newGroupInfo) => {
getGroupAdminList()
},
@ -107,13 +127,33 @@ onMounted(async () => {
//
const getGroupAdminList = () => {
if (
groupParams?.groupInfo?.group_type == 1 ||
groupParams?.groupInfo?.group_type == 3
) {
if (dialogueParams?.adminList?.length > 0) {
dialogueParams?.adminList.forEach((item) => {
if (item?.erp_user_id == userInfo?.value?.ID) {
item.is_mine = true
item.cannotRemove = true
}
})
}
state.groupAdminList = dialogueParams?.adminList
} else if (
groupParams?.groupInfo?.group_type == 2 ||
groupParams?.groupInfo?.group_type == 4
) {
let myPositionsList = []
if (groupParams?.groupInfo?.groupAdminList?.length > 0) {
groupParams?.groupInfo?.groupAdminList.forEach((groupAdminItem) => {
if (userInfo?.value?.PositionUsers?.length > 0) {
userInfo?.value?.PositionUsers.forEach((item) => {
// console.log(item.DepartmentId + '-' + item.PositionID)
if (item.PositionID === groupAdminItem.position_id) {
if (
item.PositionID === groupAdminItem.position_id &&
item.DepartmentId === groupAdminItem.dept_id
) {
myPositionsList.push(groupAdminItem)
}
})
@ -136,9 +176,10 @@ const getGroupAdminList = () => {
})
}
state.groupAdminList = groupParams?.groupInfo?.groupAdminList
}
}
//
//
const toSelectMembersPage = () => {
uni.navigateTo({
url: '/pages/chatSettings/groupManage/selectMembers?manageType=admin',
@ -147,18 +188,36 @@ const toSelectMembersPage = () => {
//
const removeGroupAdmin = (adminItem) => {
if (
groupParams?.groupInfo?.group_type == 1 ||
groupParams?.groupInfo?.group_type == 3
) {
let params = {
mode: 2, //12
group_id: dialogueParams.receiverId, //id
user_ids: String(adminItem.id),
}
console.log(params)
const resp = ServeGroupAssignAdmin(params)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
useDialogueStore().updateGroupMembers()
} else {
}
})
resp.catch(() => {})
} else if (
groupParams?.groupInfo?.group_type == 2 ||
groupParams?.groupInfo?.group_type == 4
) {
let positionInfos = []
if (state?.groupAdminList?.length > 0) {
state?.groupAdminList.forEach((item) => {
if (
positionInfos = state?.groupAdminList.filter((item) => {
return (
item.dept_id != adminItem.dept_id ||
item.position_id != adminItem.position_id
) {
positionInfos.push({
position_id: item.position_id,
position_name: item.deptPos,
})
}
)
})
let params = {
source: 'app',
@ -177,6 +236,7 @@ const removeGroupAdmin = (adminItem) => {
})
resp.catch(() => {})
}
}
}
</script>
<style scoped lang="scss">
@ -196,18 +256,23 @@ const removeGroupAdmin = (adminItem) => {
.group-admin-list {
.group-admin-list-each {
padding: 36rpx 0;
margin: 0 32rpx;
padding: 36rpx 26rpx 36rpx 14rpx;
margin: 0 18rpx;
border-bottom: 1px solid $theme-border-color;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
::v-deep .select-member-item {
padding: 0;
border-bottom: 0;
}
.group-admin-list-each-btns {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
flex-shrink: 0;
img {
width: 52rpx;
height: 52rpx;

View File

@ -11,16 +11,12 @@
:loading-more-enabled="false"
>
<template #top>
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
<div class="navBar-title flex flex-col items-center justify-center">
<span class="text-[34rpx] font-medium">
{{ $t('chat.settings.groupMember') }}
</span>
</div>
</tm-navbar>
<customNavbar :title="$t('chat.settings.groupMember')"></customNavbar>
</template>
<div class="group-members-list">
<groupMemberList
:groupType="groupParams?.groupInfo?.group_type"
:is_manager="groupParams?.groupInfo?.is_manager"
:memberList="talkParams?.memberList"
></groupMemberList>
</div>
@ -33,13 +29,18 @@ import groupMemberList from '../components/groupMembersList.vue'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'
import { ref, computed, reactive } from 'vue'
import { useDialogueStore } from '@/store'
import { useDialogueStore, useGroupStore } from '@/store'
const dialogueStore = useDialogueStore()
const talkParams = reactive({
memberList: computed(() => dialogueStore.members),
})
const groupStore = useGroupStore()
const groupParams = reactive({
groupInfo: computed(() => groupStore.groupInfo),
})
const zPaging = ref()
useZPaging(zPaging)
</script>

View File

@ -3,13 +3,7 @@
<div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
<div class="navBar-title flex flex-col items-center justify-center">
<span class="text-[34rpx] font-medium">
{{ $t('chat.settings.groupGag') }}
</span>
</div>
</tm-navbar>
<customNavbar :title="$t('chat.settings.groupGag')"></customNavbar>
</template>
<div class="manage-group-silence">
<span class="manage-group-silence-title text-[28rpx] font-regular">

View File

@ -3,11 +3,9 @@
<div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<tm-navbar
<customNavbar
:hideBack="state.editMode === 3 ? false : true"
hideHome
title=""
:leftWidth="220"
:title="$t('chat.settings.groupNotice')"
>
<template #left v-if="state.editMode !== 3">
<div class="nav-bar-cancel-btn">
@ -16,11 +14,6 @@
</span>
</div>
</template>
<div class="navBar-title flex flex-col items-center justify-center">
<span class="text-[34rpx] font-medium">
{{ $t('chat.settings.groupNotice') }}
</span>
</div>
<template #right>
<div
v-if="state.editMode !== 3"
@ -42,7 +35,7 @@
</span>
</div>
</template>
</tm-navbar>
</customNavbar>
</template>
<div class="notice-text-area">
<div class="notice-view-area">

View File

@ -11,37 +11,13 @@
@scroll="onScroll"
>
<template #top>
<tm-navbar
:hideBack="false"
hideHome
title=""
:leftWidth="220"
id="topArea"
>
<div class="navBar-title flex flex-col items-center justify-center">
<span
class="text-[34rpx] font-medium"
v-if="state.manageType === 'silence'"
>
{{ $t('chat.manage.addSilenceMember') }}
</span>
<span
class="text-[34rpx] font-medium"
v-if="state.manageType === 'admin'"
>
{{ $t('chat.manage.addAdmin') }}
</span>
<span
class="text-[34rpx] font-medium"
v-if="state.manageType === 'searchRecord'"
>
{{ $t('search.condition.member') }}
</span>
</div>
</tm-navbar>
<customNavbar :title="pageTitle" id="topArea"></customNavbar>
</template>
<div class="select-members">
<div class="search-member">
<div
class="search-member"
v-if="state.manageType !== 'removeMembers'"
>
<customInput
:searchText="state.searchText"
@inputSearchText="inputSearchText"
@ -83,7 +59,10 @@
class="member-list-alphabet-key"
:style="{
padding:
state.manageType === 'searchRecord' ? '10rpx 30rpx' : '',
state.manageType === 'searchRecord' ||
state.manageType === 'removeMembers'
? '10rpx 30rpx'
: '',
}"
v-if="alphabetItem?.memberList?.length > 0"
:id="alphabetItem.key"
@ -110,14 +89,36 @@
@clickItem="handleClickItem(item)"
:manageType="state.manageType"
:itemStyle="
state.manageType === 'searchRecord' ? 'list' : 'card'
state.manageType === 'searchRecord' ||
state.manageType === 'removeMembers'
? 'list'
: 'card'
"
>
<template
#left
v-if="state.manageType !== 'searchRecord'"
>
<div
v-if="
state.manageType === 'removeMembers' &&
item?.is_mine
"
>
<tm-checkbox
color="#fff"
:transprent="true"
:border="0"
:disabled="true"
></tm-checkbox>
</div>
<tm-checkbox
v-if="
!(
state.manageType === 'removeMembers' &&
item?.is_mine
)
"
:round="10"
:color="
item?.checkArr?.length > 0 ? '#46299d' : '#B4B4B4'
@ -138,6 +139,7 @@
(state.manageType === 'admin' &&
(item.leader === 1 || item.leader === 2))
"
@change="checkBoxChange"
></tm-checkbox>
</template>
</selectMemberItem>
@ -149,10 +151,31 @@
</div>
<template #bottom v-if="state.manageType !== 'searchRecord'">
<customBtn
v-if="state.manageType !== 'removeMembers'"
:isBottom="true"
:btnText="$t('ok')"
@clickBtn="confirmSilenceMember"
@clickBtn="confirmSelectMembers"
></customBtn>
<div
class="confirm-btn-area"
v-if="state.manageType === 'removeMembers'"
>
<div class="confirm-btn-area-statistic-text">
<span class="text-[28rpx] font-medium">
{{
$t('select.member.num') +
'' +
state.selectedMembersNum +
$t('statistic.unit.person')
}}
</span>
</div>
<customBtn
:btnText="$t('ok')"
@clickBtn="confirmSelectMembers"
:disabled="state.selectedMembersNum == 0 ? true : false"
></customBtn>
</div>
</template>
</ZPaging>
</div>
@ -166,8 +189,15 @@ import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'
import { computed, onMounted, reactive, ref, watch, nextTick } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { ServeGroupNoSpeak, ServeEditGroupAdmin } from '@/api/group/index.js'
import {
ServeGroupNoSpeak,
ServeEditGroupAdmin,
ServeGroupAssignAdmin,
ServeRemoveMembersGroup,
} from '@/api/group/index.js'
import { useDialogueStore, useGroupStore, useGroupTypeStore } from '@/store'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const zPaging = ref()
useZPaging(zPaging)
@ -203,6 +233,8 @@ const state = reactive({
currentAlphabet: 'A', //A-Z
scrollDirection: '', //
isAssign: false, //view
selectedMembersNum: 0, //
isCreateDepGroup: 0, //
})
watch(
@ -232,6 +264,10 @@ onLoad((options) => {
state.manageType = options.manageType
assembleAlphabetMemberList(dialogueParams?.memberList)
}
if (options.isCreateDepGroup) {
state.isCreateDepGroup = Number(options.isCreateDepGroup)
assembleAlphabetMemberList()
}
})
onMounted(() => {
@ -263,6 +299,21 @@ onMounted(() => {
})
})
//
const pageTitle = computed(() => {
let page_title = ''
if (state.manageType === 'silence') {
page_title = t('chat.manage.addSilenceMember')
} else if (state.manageType === 'admin') {
page_title = t('chat.manage.addAdmin')
} else if (state.manageType === 'searchRecord') {
page_title = t('search.condition.member')
} else if (state.manageType === 'removeMembers') {
page_title = t('select.member.remove')
}
return page_title
})
//
const handleIntersection = (entries) => {
if (state.isAssign) {
@ -303,7 +354,9 @@ const inputSearchText = (e) => {
const handleClickItem = (item) => {
if (
(state.manageType === 'silence' && item.is_mute === 1) ||
(state.manageType === 'admin' && (item.leader === 1 || item.leader === 2))
(state.manageType === 'admin' &&
(item.leader === 1 || item.leader === 2)) ||
(state.manageType === 'removeMembers' && item.is_mine)
) {
return
}
@ -311,26 +364,33 @@ const handleClickItem = (item) => {
if (
state.manageType === 'admin' &&
(groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4)
groupParams.groupInfo.group_type == 4 ||
state.isCreateDepGroup === 1)
) {
itemList = state.resultMemberList[0].memberList
}
itemList.forEach((ele) => {
if (ele.id == item.id) {
ele.checkArr = ele.checkArr?.length > 0 ? [] : [item.id]
if (ele.checkArr?.length > 0) {
state.selectedMembersNum += 1
} else {
state.selectedMembersNum -= 1
}
}
})
}
//
const confirmSilenceMember = () => {
//
const confirmSelectMembers = () => {
let selectedUserIds = ''
let itemList = dialogueParams.memberList
let positionInfos = []
if (
state.manageType === 'admin' &&
(groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4)
groupParams.groupInfo.group_type == 4 ||
state.isCreateDepGroup === 1)
) {
itemList = state.resultMemberList[0].memberList
}
@ -346,8 +406,16 @@ const confirmSilenceMember = () => {
ele.checkArr?.length > 0 ||
(ele.leader && (ele.leader == 1 || ele.leader == 2))
) {
if (state.isCreateDepGroup === 1) {
let posInfo = Object.assign({}, ele.positionInfo, {
name: ele.nickname,
id: ele.id,
})
positionInfos.push(posInfo)
} else {
positionInfos.push(ele.positionInfo)
}
}
})
console.log(selectedUserIds)
if (selectedUserIds) {
@ -368,7 +436,33 @@ const confirmSilenceMember = () => {
})
resp.catch(() => {})
} else if (state.manageType === 'admin') {
if (state.isCreateDepGroup === 1) {
// console.log(positionInfos)
groupTypeStore.groupAdmins.value = positionInfos
uni.navigateBack({
delta: 1,
})
} else {
if (
groupParams.groupInfo.group_type == 1 ||
groupParams.groupInfo.group_type == 3
) {
let params = {
mode: 1, //12
group_id: dialogueParams.receiverId, //id
user_ids: selectedUserIds,
}
console.log(params)
const resp = ServeGroupAssignAdmin(params)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
useDialogueStore().updateGroupMembers()
} else {
}
})
resp.catch(() => {})
} else if (
groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4
) {
@ -390,12 +484,32 @@ const confirmSilenceMember = () => {
resp.catch(() => {})
}
}
} else if (state.manageType === 'removeMembers') {
let params = {
group_id: dialogueParams.receiverId, //id
members_ids: selectedUserIds, //id
}
console.log(params)
const resp = ServeRemoveMembersGroup(params)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
useDialogueStore().updateGroupMembers()
groupStore.ServeGroupDetail()
} else {
}
})
resp.catch(() => {})
}
}
}
//A-Z
const assembleAlphabetMemberList = async (newMemberList) => {
if (state.manageType === 'searchRecord') {
if (
state.manageType === 'searchRecord' ||
state.manageType === 'removeMembers'
) {
const resultMemberList = ref([])
const alphabet = Array.from({ length: 26 }, (_, i) =>
String.fromCharCode(i + 65),
@ -425,6 +539,27 @@ const assembleAlphabetMemberList = async (newMemberList) => {
departmentIdsArr.push(item.dept_id)
})
}
getPosiByDep(departmentIdsArr)
} else if (state.isCreateDepGroup === 1) {
let departmentIdsArr = []
if (groupTypeStore?.depCheckedKeys?.value?.length > 0) {
groupTypeStore.depCheckedKeys.value.forEach((item) => {
departmentIdsArr.push(item.ID)
})
}
getPosiByDep(departmentIdsArr)
} else {
state.resultMemberList = [
{
key: '',
memberList: newMemberList,
},
]
}
}
}
const getPosiByDep = async (departmentIdsArr) => {
await groupTypeStore.getPositionByDepartment({
IDs: departmentIdsArr,
})
@ -437,6 +572,8 @@ const assembleAlphabetMemberList = async (newMemberList) => {
id: item.ID + '-' + positionItem.ID,
checkArr: [],
positionInfo: {
dept_id: item.ID,
dept_name: item.name,
position_id: positionItem.ID,
position_name: positionItem.name,
},
@ -453,22 +590,32 @@ const assembleAlphabetMemberList = async (newMemberList) => {
})
})
}
// console.log(departmentAllPositions)
if (
state.isCreateDepGroup === 1 &&
groupTypeStore?.groupAdmins?.value?.length > 0
) {
departmentAllPositions.forEach((allPos) => {
groupTypeStore.groupAdmins.value.forEach((admin) => {
if (allPos.id === admin.id) {
allPos.checkArr = [allPos.id]
}
})
})
}
if(state?.searchText){
const lowerCaseSearchText = state?.searchText.toLowerCase()
departmentAllPositions = departmentAllPositions.filter((item) =>
state?.searchText
? item.nickname.toLowerCase().includes(lowerCaseSearchText)
: true,
)
}
state.resultMemberList = [
{
key: '',
memberList: departmentAllPositions,
},
]
} else {
state.resultMemberList = [
{
key: '',
memberList: newMemberList,
},
]
}
}
}
//view
@ -494,6 +641,15 @@ const onScroll = (e) => {
state.scrollDirection = ''
}
}
//
const checkBoxChange = (e) => {
if (e) {
state.selectedMembersNum += 1
} else {
state.selectedMembersNum -= 1
}
}
</script>
<style scoped lang="scss">
.outer-layer {
@ -544,4 +700,28 @@ const onScroll = (e) => {
}
}
}
.confirm-btn-area {
background-color: #fff;
padding: 14rpx 32rpx 72rpx;
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
.confirm-btn-area-statistic-text {
margin: 18rpx 0 0;
span {
line-height: 40rpx;
color: $theme-text;
}
}
::v-deep .custom-btn-class {
padding: 18rpx 104rpx !important;
}
::v-deep .is-disabled {
background-color: #e6e6e6 !important;
color: #bebebe !important;
}
}
</style>

View File

@ -3,18 +3,20 @@
<div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
<div class="navBar-title flex flex-col items-center justify-center">
<span class="text-[34rpx] font-medium">
{{ $t('index.chat.settings') }}
</span>
</div>
</tm-navbar>
<customNavbar :title="$t('index.chat.settings')"></customNavbar>
</template>
<div class="chat-settings">
<div class="chat-group-base-infos chat-settings-card">
<div
class="chat-group-base-infos chat-settings-card"
v-if="dialogueParams.type === 2"
>
<div class="base-info-avatar" @click="toEditAvatarPage">
<img :src="groupAvatar" />
<avatarModule
:mode="2"
:avatar="groupParams?.groupInfo?.avatar"
:groupType="groupParams?.groupInfo?.group_type"
:customStyle="{ width: '96rpx', height: '96rpx' }"
></avatarModule>
</div>
<div class="base-info">
<div class="base-info-name">
@ -24,6 +26,7 @@
</span>
</div>
<div
v-if="groupParams?.groupInfo?.group_type !== 1"
class="base-info-tag"
:style="{
borderColor:
@ -62,9 +65,17 @@
@toManagePage="toManagePage"
></settingFormItem>
<groupMemberList
:groupType="groupParams?.groupInfo?.group_type"
:is_manager="groupParams?.groupInfo?.is_manager"
:memberList="dialogueParams?.memberList"
:memberListsLimit="groupParams?.groupInfo?.is_manager ? 13 : 14"
:memberListsLimit="
groupParams?.groupInfo?.group_type == 1 ||
groupParams?.groupInfo?.group_type == 3
? groupParams?.groupInfo?.is_manager
? 13
: 14
: 15
"
></groupMemberList>
</div>
</div>
@ -125,22 +136,31 @@
</div>
</div>
<div class="clear-chat-record-btn chat-settings-card">
<div class="clear-chat-record-btn-each">
<div
@click="showConfirmPrompt(1)"
class="clear-chat-record-btn-each"
>
<span class="text-[32rpx] font-regular">
{{ $t('chat.settings.clearChatRecord') }}
</span>
</div>
<div
@click="showConfirmPrompt(2)"
class="clear-chat-record-btn-each"
v-if="groupParams?.groupInfo?.is_manager"
v-if="
groupParams?.groupInfo?.is_manager && dialogueParams.type === 2
"
>
<span class="text-[32rpx] font-regular">
{{ $t('group.disband.btn') }}
</span>
</div>
<div
@click="showConfirmPrompt(3)"
class="clear-chat-record-btn-each"
v-if="groupParams?.groupInfo?.is_manager"
v-if="
groupParams?.groupInfo?.is_manager && dialogueParams.type === 2
"
>
<span class="text-[32rpx] font-regular">
{{ $t('group.quit.btn') }}
@ -153,10 +173,8 @@
</div>
</template>
<script setup>
import zu4992 from '@/static/image/chatList/zu4992@2x.png'
import zu4991 from '@/static/image/chatList/zu4991@2x.png'
import zu4989 from '@/static/image/chatList/zu4989@2x.png'
import zu5296 from '@/static/image/chatList/zu5296@2x.png'
import avatarModule from '@/components/avatar-module/index.vue'
import useConfirm from '@/components/x-confirm/useConfirm.js'
import recordSearchTypeIcon_groupMember from '@/static/image/chatSettings/recordSearchTypeGroupMembers.png'
import recordSearchTypeIcon_date from '@/static/image/chatSettings/recordSearchTypeDate.png'
import recordSearchTypeIcon_imgAndVideo from '@/static/image/chatSettings/recordSearchTypeImgAndVideo.png'
@ -171,12 +189,19 @@ import {
useTalkStore,
useDialogueStore,
useGroupStore,
useGroupTypeStore,
} from '@/store'
import { onLoad } from '@dcloudio/uni-app'
import {
ServeInviteGroup,
ServeDismissGroup,
ServeSecedeGroup,
} from '@/api/group/index'
import { ServeTopTalkList, ServeSetNotDisturb } from '@/api/chat/index'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
import customInput from '@/components/custom-input/custom-input.vue'
const { showConfirm } = useConfirm()
const userStore = useUserStore()
const talkStore = useTalkStore()
const dialogueStore = useDialogueStore()
@ -218,6 +243,14 @@ watch(
{ deep: true },
)
watch(
() => useGroupTypeStore()?.allChooseMembers?.value,
(newMembers) => {
// console.log(newMembers)
inviteMembersInGroup(newMembers)
},
)
onLoad(async (options) => {
console.log(dialogueParams)
if (options.groupId) {
@ -247,10 +280,6 @@ onLoad(async (options) => {
onMounted(() => {
state.recordSearchTypeList = [
{
value: t('chat.settings.groupMember'),
typeIcon: recordSearchTypeIcon_groupMember,
},
{
value: t('record.searchType.date'),
typeIcon: recordSearchTypeIcon_date,
@ -268,6 +297,12 @@ onMounted(() => {
typeIcon: recordSearchTypeIcon_link,
},
]
if (dialogueParams.type === 2) {
state.recordSearchTypeList.unshift({
value: t('chat.settings.groupMember'),
typeIcon: recordSearchTypeIcon_groupMember,
})
}
state.chatSettings = [
{
label: t('chat.settings.topSession'),
@ -286,14 +321,6 @@ onMounted(() => {
]
})
//
const groupAvatar = computed(() => {
return (
groupParams?.groupInfo?.avatar ||
groupTypeMapping[groupParams?.groupInfo?.group_type]?.defaultImg
)
})
//
const groupName = computed(() => {
return groupParams?.groupInfo?.group_name
@ -306,26 +333,21 @@ const groupNum = computed(() => {
// -groupType
const groupTypeMapping = {
0: {
defaultImg: 'textImg',
},
0: {},
1: {
defaultImg: zu4992,
result_type: t('index.mine.normal'),
},
2: {
result_type: t('index.mine.department'),
result_type_color: '#377EC6',
defaultImg: zu4989,
},
3: {
result_type: t('index.mine.project'),
result_type_color: '#C1681C',
defaultImg: zu4991,
},
4: {
result_type: t('index.type.company'),
result_type_color: '#7A58DE',
defaultImg: zu5296,
},
}
@ -399,9 +421,7 @@ const updateGroupInfos = () => {
//
const toEditGroupInfoPage = () => {
uni.navigateTo({
url:
'/pages/chatSettings/groupManage/editGroupName?groupAvatar=' +
groupAvatar.value,
url: '/pages/chatSettings/groupManage/editGroupName',
})
}
@ -411,9 +431,7 @@ const toEditAvatarPage = () => {
return
}
uni.navigateTo({
url:
'/pages/chatSettings/groupManage/editAvatar?groupAvatar=' +
groupAvatar.value,
url: '/pages/chatSettings/groupManage/editAvatar',
})
}
@ -426,9 +444,7 @@ const toManagePage = (label) => {
return
}
uni.navigateTo({
url:
'/pages/chatSettings/groupManage/editGroupName?groupAvatar=' +
groupAvatar.value,
url: '/pages/chatSettings/groupManage/editGroupName',
})
} else if (label === t('chat.settings.groupNotice')) {
uni.navigateTo({
@ -455,19 +471,23 @@ const toManagePage = (label) => {
//
const toSearchByConditionPage = (flag) => {
let condition = ''
if (flag == 0) {
let flagIndex = 0
if (dialogueParams.type === 1) {
flagIndex = -1
}
if (flag == flagIndex) {
uni.navigateTo({
url:
'/pages/chatSettings/groupManage/selectMembers?manageType=searchRecord',
})
} else {
if (flag == 1) {
if (flag == flagIndex + 1) {
condition = 'date'
} else if (flag == 2) {
} else if (flag == flagIndex + 2) {
condition = 'imgAndVideo'
} else if (flag == 3) {
} else if (flag == flagIndex + 3) {
condition = 'file'
} else if (flag == 4) {
} else if (flag == flagIndex + 4) {
condition = 'link'
}
uni.navigateTo({
@ -519,6 +539,89 @@ const changeSwitch = (switchStatus, label) => {
resp.catch(() => {})
}
//
const showConfirmPrompt = (flag) => {
let confirmContent = ''
let subContent = ''
let subContentColor = ''
if (flag === 1) {
//
confirmContent = t('ok') + t('chat.settings.clearChatRecord')
} else if (flag === 2) {
//
confirmContent = t('ok') + t('group.quit.btn')
subContent = t('groupManage.disband.hint')
subContentColor = '#CF3050'
} else if (flag === 3) {
//退
confirmContent = t('ok') + t('group.quit.btn')
subContent = t('groupManage.quit.hint')
subContentColor = '#B4B4B4'
}
showConfirm({
subContent: subContent,
subContentColor: subContentColor,
confirmColor: '#CF3050',
content: confirmContent,
contentText: t('ok'),
confirmText: t('ok'),
cancelText: t('cancel'),
onConfirm: async () => {
if (flag === 1) {
dialogueStore.apiClearRecord()
} else if (flag === 2) {
let params = {
group_id: dialogueParams.receiver_id, //id
}
console.log(params)
const res = await ServeDismissGroup(params)
if (res.code === 200) {
dialogueStore.updateGroupMembers()
groupStore.ServeGroupDetail()
uni.navigateBack({
delta: 2,
})
}
} else if (flag === 3) {
ServeSecedeGroup({
group_id: dialogueParams.receiver_id,
}).then(({ code, message }) => {
if (code == 200) {
// dialogueStore.apiClearRecord()
} else {
}
})
}
},
onCancel: () => {},
})
}
///
const inviteMembersInGroup = async (memberList) => {
if (memberList?.length > 0) {
let erp_ids = ''
memberList.forEach((item) => {
if (!erp_ids) {
erp_ids = item.ID
} else {
erp_ids += ',' + item.ID
}
})
let params = {
group_id: dialogueParams.receiver_id, //id
erp_ids: String(erp_ids), //erpid
}
console.log(params)
const res = await ServeInviteGroup(params)
if (res.code === 200) {
dialogueStore.updateGroupMembers()
groupStore.ServeGroupDetail()
useGroupTypeStore()?.resetGroupInfo()
}
}
}
</script>
<style scoped lang="scss">
.outer-layer {
@ -555,16 +658,7 @@ const changeSwitch = (switchStatus, label) => {
padding: 32rpx 40rpx 32rpx 20rpx;
.base-info-avatar {
width: 96rpx;
height: 96rpx;
flex-shrink: 0;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
}
.base-info {
width: 100%;

File diff suppressed because it is too large Load Diff

View File

@ -1,36 +1,58 @@
<template>
<div>
<div @click="cellClick" :class="['chatItem', props.data.is_top === 1 ? 'isTop' : '']">
<div
@click="cellClick"
:class="['chatItem', props.data.is_top === 1 ? 'isTop' : '']"
>
<div>
<tm-checkbox v-if="props.isMultiSelect" :round="10" :modelValue="props.data.isCheck"
@update:modelValue="props.data.isCheck = !props.data.isCheck" :size="42" :color="props.data.isCheck ? '#46299D' : '#BABABA'"></tm-checkbox>
<tm-checkbox
v-if="props.isMultiSelect"
:round="10"
:modelValue="props.data.isCheck"
@update:modelValue="props.data.isCheck = !props.data.isCheck"
:size="32"
:color="props.data.isCheck ? '#46299D' : '#BABABA'"
class="pr-[4rpx]"
></tm-checkbox>
</div>
<div class="avatarImg">
<tm-image :width="96" :height="96" :round="12" :src="props.data.avatar"></tm-image>
<avatarModule
:mode="2"
:avatar="props?.data?.avatar"
:groupType="props?.data?.group_type"
:customStyle="{ width: '96rpx', height: '96rpx' }"
></avatarModule>
</div>
<div class="chatInfo">
<div class="chatInfo_1">
<div class="flex items-center">
<div class="text-[#000000] text-[32rpx] font-bold opacity-90">{{ props.data.name }}</div>
<div
class="text-[#171717] text-[32rpx] font-medium leading-[44rpx]"
>
<span>{{ props.data.name }}</span>
<span>{{ props.data.group_member_num }}</span>
</div>
</div>
</div>
</div>
<div v-if="props.index !== talkStore.talkItems.length - 1" class="divider"></div>
</div>
<div
v-if="props.index !== talkStore.talkItems.length - 1"
class="divider"
></div>
</div>
</template>
<script setup>
import { ref, reactive, defineProps, defineEmits } from "vue"
import dayjs from "dayjs";
import avatarModule from '@/components/avatar-module/index.vue'
import { ref, reactive, defineProps, defineEmits } from 'vue'
import dayjs from 'dayjs'
import { beautifyTime } from '@/utils/datetime'
import { ServeClearTalkUnreadNum } from '@/api/chat'
import { useTalkStore, useDialogueStore } from '@/store'
import { useSessionMenu } from '@/hooks'
const talkStore = useTalkStore()
const {
onToTopTalk
} = useSessionMenu()
const { onToTopTalk } = useSessionMenu()
const dialogueStore = useDialogueStore()
const emit = defineEmits(['itemClick'])
const props = defineProps({
@ -48,25 +70,22 @@ const props = defineProps({
type: Boolean,
default: false,
},
});
})
const cellClick = () => {
emit('itemClick', props.data)
}
</script>
<style lang="scss" scoped>
.chatItem {
width: 100%;
height: 154rpx;
padding: 30rpx 16rpx;
padding: 24rpx 18rpx;
display: flex;
align-items: center;
&.isTop {
background-color: #F3F3F3;
background-color: #f3f3f3;
}
}
@ -77,7 +96,7 @@ const cellClick = () => {
.chatInfo {
flex: 1;
margin-left: 20rpx;
margin-left: 16rpx;
}
.chatInfo_1 {
@ -97,17 +116,16 @@ const cellClick = () => {
font-size: 28rpx;
color: #000000;
opacity: 40%;
}
.companyTag {
width: 76rpx;
height: 38rpx;
border: 1px solid #7A58DE;
border: 1px solid #7a58de;
font-size: 24rpx;
text-align: center;
border-radius: 6rpx;
color: #7A58DE;
color: #7a58de;
font-weight: bold;
margin-left: 12rpx;
}

View File

@ -1,74 +1,123 @@
<template>
<div class="outer-layer">
<div>
<tm-navbar :hideBack="true" hideHome :leftWidth="320">
<template v-slot:left>
<div @click="handleClose" class="ml-[48rpx] text-[34rpx] leading-[40rpx]">
关闭
<div class="choose-chat-page">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<customNavbar
:title="$t('pageTitle.select.shareChat')"
:hideBack="true"
>
<template #left>
<div
@click="handleClose"
class="ml-[42rpx] text-[34rpx] leading-[48rpx] font-regular"
>
{{ $t('button.text.close') }}
</div>
</template>
<template v-slot:default>
<div class="text-[34rpx] font-bold ml-[-80rpx] leading-[40rpx]">
选择一个聊天
</div>
</template>
<template v-slot:right>
<div v-if="!isMultiSelect" @click="isMultiSelect = true" class="mr-[36rpx] text-[34rpx] leading-[40rpx]">
多选
<template #right>
<div
v-if="!isMultiSelect"
@click="isMultiSelect = true"
class="mr-[36rpx] text-[34rpx] leading-[48rpx] font-regular"
>
{{ $t('button.multiple.choice') }}
</div>
<div v-else class="mr-[36rpx] btnBox">
<tm-button @click="handleFinish" color="#5B3FB1" :fontSize="34" :disabled="!selectItems.length"
:margin="[10]" :shadow="0" size="small" :width="selectItems.length ? 162 : 116"
:label="selectItems.length ? `完成(${selectItems.length})` : '完成'">
</tm-button>
<customBtn
:btnText="
selectItems.length
? $t('button.text.done') + `(${selectItems.length})`
: $t('button.text.done')
"
:disabled="!selectItems.length"
@clickBtn="handleFinish"
></customBtn>
</div>
</template>
</tm-navbar>
</div>
<div class="root">
</customNavbar>
</template>
<div class="choose-chat">
<div class="searchRoot">
<tm-input placeholder="请输入…" color="#F9F9FD" :round="1" prefix="tmicon-search" prefixColor="#46299D"></tm-input>
<customInput></customInput>
</div>
<div class="contentRoot">
<chatItem v-for="(item, index) in items" :key="item.index_name" :index="index" :data="item"
:isMultiSelect="isMultiSelect" @itemClick="itemClick" />
<chatItem
v-for="(item, index) in items"
:key="item.index_name"
:index="index"
:data="item"
:isMultiSelect="isMultiSelect"
@itemClick="itemClick"
/>
</div>
</div>
<tm-modal v-model:show="showWin" :height="forwardModalHeight" :round="4" okColor="#FFFFFF" @ok="handleOk"
@cancel="handleCancel">
<tm-modal
v-model:show="showWin"
:height="forwardModalHeight"
:round="4"
okColor="#FFFFFF"
@ok="handleOk"
@cancel="handleCancel"
>
<template v-slot:title>
<div class="w-full pl-[30rpx] leading-[48rpx] mt-[30rpx] font-bold text-[32rpx]">
<div
class="w-full pl-[30rpx] leading-[48rpx] mt-[30rpx] font-bold text-[32rpx]"
>
发送给:
</div>
</template>
<template v-slot:default>
<div class="pl-[22rpx] pr-[32rpx] pt-[10rpx] flex items-center justify-start flex-wrap">
<div v-if="selectItemsModal.length > 1" v-for="item in selectItemsModal"
class="w-1/4 mt-[20rpx] flex flex-col items-center justify-center">
<div
class="pl-[22rpx] pr-[32rpx] pt-[10rpx] flex items-center justify-start flex-wrap"
>
<div
v-if="selectItemsModal.length > 1"
v-for="item in selectItemsModal"
class="w-1/4 mt-[20rpx] flex flex-col items-center justify-center"
>
<div class="w-[96rpx] h-[96rpx] rounded-[60rpx] flex-center">
<tm-image :width="96" :height="96" :round="12" :src="item.avatar"></tm-image>
<tm-image
:width="96"
:height="96"
:round="12"
:src="item.avatar"
></tm-image>
</div>
<div
class="mt-[8rpx] text-[#666666] text-[24rpx] w-[94rpx] truncate"
>
{{ item.name }}
</div>
<div class="mt-[8rpx] text-[#666666] text-[24rpx] w-[94rpx] truncate">{{ item.name }}</div>
</div>
<div v-else class="w-full flex items-center justify-start">
<div class="w-[96rpx] h-[96rpx] rounded-[60rpx] flex-center">
<tm-image :width="96" :height="96" :round="12" :src="selectItemsModal[0].avatar"></tm-image>
<avatarModule
:mode="2"
:avatar="selectItemsModal[0].avatar"
:groupType="selectItemsModal[0].group_type"
:customStyle="{ width: '96rpx', height: '96rpx' }"
></avatarModule>
<div class="ml-[16rpx] text-[#161616] text-[32rpx] font-bold">
{{ selectItemsModal[0].name }}
</div>
<div class="ml-[16rpx] text-[#161616] text-[32rpx] font-bold ">{{ selectItemsModal[0].name }}</div>
</div>
</div>
</template>
</tm-modal>
</ZPaging>
</div>
</template>
<script setup>
import { ref, watch, computed, onMounted, onUnmounted } from 'vue';
import { onShow, onLoad } from "@dcloudio/uni-app";
import { useChatList } from "@/store/chatList/index.js";
import { useAuth } from "@/store/auth";
import customInput from '@/components/custom-input/custom-input.vue'
import avatarModule from '@/components/avatar-module/index.vue'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue'
import { ref, watch, computed, onMounted, onUnmounted } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import { useTalkStore, useUserStore, useDialogueStore } from '@/store'
import chatItem from './components/chatItem.vue';
import addCircle from "@/static/image/chatList/addCircle.png";
import chatItem from './components/chatItem.vue'
import addCircle from '@/static/image/chatList/addCircle.png'
import lodash from 'lodash'
const talkStore = useTalkStore()
@ -87,13 +136,13 @@ const items = ref([])
// })
const selectItems = computed(() => {
return items.value.filter(item => item.isCheck)
return items.value.filter((item) => item.isCheck)
})
const selectItemsModal = ref([])
const itemClick = (item) => {
console.log(item);
console.log(item)
if (isMultiSelect.value) {
item.isCheck = !item.isCheck
return false
@ -104,7 +153,7 @@ const itemClick = (item) => {
}
const handleFinish = () => {
console.log(selectItems.value);
console.log(selectItems.value)
selectItemsModal.value = selectItems.value
if (selectItemsModal.value.length > 4) {
forwardModalHeight.value = 560
@ -123,7 +172,6 @@ const handleClose = () => {
const handleOk = () => {
let msg_ids = dialogueStore.forwardMessages.map((item) => item.msg_id)
let user_ids = []
let group_ids = []
@ -139,23 +187,27 @@ const handleOk = () => {
mode: dialogueStore.forwardType,
message_ids: msg_ids,
uids: user_ids,
gids: group_ids
gids: group_ids,
})
uni.navigateBack()
}
const handleCancel = () => {
console.log('cancel');
console.log('cancel')
}
watch(() => talkStore, (newValue, oldValue) => {
console.log(talkStore);
}, { deep: true, immediate: true })
watch(
() => talkStore,
(newValue, oldValue) => {
console.log(talkStore)
},
{ deep: true, immediate: true },
)
onMounted(() => {
talkStore.loadTalkList()
console.log(talkStore.talkItems)
items.value = lodash.cloneDeep(talkStore.talkItems)
})
onUnmounted(() => {
@ -164,24 +216,20 @@ onUnmounted(() => {
})
</script>
<style scoped lang="scss">
uni-page-body,
page {
::v-deep .zp-paging-container-content {
height: 100%;
display: flex;
}
.outer-layer {
overflow-y: auto;
.choose-chat {
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
background-size: cover;
padding: 0 32rpx 20rpx 32rpx;
padding: 20rpx 32rpx;
display: flex;
flex-direction: column;
}
.root {
flex: 1;
padding: 20rpx 0;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
background-position: bottom center;
background-attachment: fixed;
}
.searchRoot {
@ -195,33 +243,38 @@ page {
}
.btnBox {
:deep(.button) {
&[disabled="true"] {
background-color: #F3F3F3 !important;
color: #BABABA !important;
::v-deep .custom-btn-class {
padding: 6rpx 24rpx !important;
width: unset !important;
min-width: unset !important;
height: unset !important;
}
::v-deep .wd-button__text {
font-size: 34rpx !important;
font-weight: 400 !important;
line-height: 48rpx !important;
}
}
:deep(.noNvueBorder.round-bl-4) {
border-top: 1rpx solid #E7E7E7 !important;
background-color: #FFFFFF !important;
border-top: 1rpx solid #e7e7e7 !important;
background-color: #ffffff !important;
uni-text {
color: #1A1A1A !important;
color: #1a1a1a !important;
font-size: 32rpx !important;
line-height: 48rpx !important;
}
}
:deep(.noNvueBorder.round-br-4) {
border-top: 1rpx solid #E7E7E7 !important;
border-left: 1rpx solid #E7E7E7 !important;
background-color: #FFFFFF !important;
border-top: 1rpx solid #e7e7e7 !important;
border-left: 1rpx solid #e7e7e7 !important;
background-color: #ffffff !important;
uni-text {
font-weight: bold;
color: #46299D !important;
color: #46299d !important;
font-size: 32rpx !important;
line-height: 48rpx !important;
}

View File

@ -1,12 +1,11 @@
<template>
<div class="outer-layer">
<div>
<tm-navbar :hideBack="false" hideHome title="选择部门"> </tm-navbar>
</div>
<div class="root">
<div
class="w-full h-[1134rpx] mb-[20rpx] pl-[32rpx] pr-[32rpx] pb-[20rpx] overflow-y-auto"
>
<div class="choose-deps-page">
<zPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<customNavbar :title="$t('pageTitle.select.department')"></customNavbar>
</template>
<div class="choose-deps">
<div class="w-full pl-[32rpx] pr-[32rpx] overflow-y-auto">
<div class="pl-[16rpx] pr-[16rpx] pt-[22rpx] pb-[24rpx] bg-[#FFFFFF]">
<tm-input
placeholder="请输入…"
@ -48,7 +47,7 @@
</div>
<div
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
@click="()=>allCheck(allCheckStatus)"
@click="() => allCheck(allCheckStatus)"
>
<div>
<checkBox
@ -66,9 +65,7 @@
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
>
<div class="w-full flex items-center justify-between">
<div
class="flex items-center"
>
<div class="flex items-center">
<div>
<checkBox
:disabled="!item?.sons?.length"
@ -80,14 +77,19 @@
{{ item.name }}
</div>
</div>
<div v-if="item.sons?.length" class="flex items-center mr-[32rpx]">
<div
v-if="item.sons?.length"
class="flex items-center mr-[32rpx]"
>
<div class="vDivider mr-[32rpx]"></div>
<div @click="() => toNextLevel(item)" class="flex items-center">
<div class="mr-[12rpx]">
<tm-image
:width="26"
:height="26"
:src="item.checkStatus !== 'checked' ? downDep : downDepDis"
:src="
item.checkStatus !== 'checked' ? downDep : downDepDis
"
></tm-image>
</div>
<div
@ -105,18 +107,29 @@
</div>
</div>
</div>
</div>
<template #bottom>
<div class="h-[162rpx] pl-[32rpx] pr-[32rpx] bg-[#FFFFFF]">
<div class="mt-[14rpx] flex justify-between">
<div class="flex flex-col">
<div @click="openDrawer" class="flex items-center text-[28rpx] leading-[60rpx] text-[#000000] font-bold">
<div
@click="openDrawer"
class="flex items-center text-[28rpx] leading-[60rpx] text-[#000000] font-bold"
>
<div>已选择部门数</div>
<div>{{ allCheckedList.length }}</div>
<div class="ml-[28rpx]">
<tm-icon :fontSize="24" color="#46299D" name="tmicon-angle-up"></tm-icon>
<tm-icon
:fontSize="24"
color="#46299D"
name="tmicon-angle-up"
></tm-icon>
</div>
</div>
<div class="text-[24rpx] leading-[24rpx] text-[#7A58DE] w-[280rpx] truncate">
{{ allCheckedList.map(v=>v.name).toString() }}
<div
class="text-[24rpx] leading-[24rpx] text-[#7A58DE] w-[280rpx] truncate"
>
{{ allCheckedList.map((v) => v.name).toString() }}
</div>
</div>
<div class="btnBox">
@ -131,9 +144,7 @@
:height="76"
size="large"
label="确定"
>
</tm-button>
</div>
></tm-button>
</div>
</div>
</div>
@ -144,50 +155,68 @@
:hideHeader="true"
:round="5"
>
<div class="flex flex-col w-full h-full pt-[36rpx] pl-[32rpx] pr-[32rpx] leading-[60rpx]" >
<div class="text-[32rpx] font-bold flex items-center justify-between" >
<div
class="flex flex-col w-full h-full pt-[36rpx] pl-[32rpx] pr-[32rpx] leading-[60rpx]"
>
<div
class="text-[32rpx] font-bold flex items-center justify-between"
>
<div class="flex items-center ml-[10rpx]">
<div>已选择部门数</div>
<div>{{ allCheckedList.length }}</div>
</div>
<div
class="text-[#7A58DE] mr-[10rpx]"
@click="()=>showWin = false"
@click="() => (showWin = false)"
>
确定
</div>
</div>
<div class="flex-1 pb-[20rpx] overflow-y-auto pt-[30rpx]" >
<div class="flex-1 pb-[20rpx] overflow-y-auto pt-[30rpx]">
<div
v-for="(item,index) in allCheckedList"
v-for="(item, index) in allCheckedList"
class="flex flex-col"
>
<div v-if="index ===0" class="divider" ></div>
<div v-if="index === 0" class="divider"></div>
<div
class="flex items-center justify-between mt-[36rpx] font-bold text-[#000000] leading-[54rpx] mb-[34rpx]"
>
<div class="text-[28rpx] ml-[10rpx]">{{ item.name }}</div>
<div class="diyBtn">
<tm-button @click="()=>deleteNode(item)" :disabled="userDepIds.includes(item.ID)" :margin="[10]" :shadow="0" text size="small" :width="106" :height="50" outlined label="移除"></tm-button>
<tm-button
@click="() => deleteNode(item)"
:disabled="userDepIds.includes(item.ID)"
:margin="[10]"
:shadow="0"
text
size="small"
:width="106"
:height="50"
outlined
label="移除"
></tm-button>
</div>
</div>
<div class="divider" ></div>
<div class="divider"></div>
</div>
</div>
</div>
</tm-drawer>
</template>
</zPaging>
</div>
</template>
<script setup>
import { ref, watch, computed, onMounted,nextTick } from "vue";
import { onShow, onLoad } from "@dcloudio/uni-app";
import { useChatList } from "@/store/chatList/index.js";
import { useAuth } from "@/store/auth";
import { useTalkStore, useUserStore } from "@/store";
import { useGroupTypeStore } from "@/store/groupType";
import downDep from "@/static/image/chatList/downDep.png";
import downDepDis from "@/static/image/chatList/downDepDis.png";
import checkBox from "@/components/checkBox/index.vue";
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { ref, watch, computed, onMounted, nextTick } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import { useTalkStore, useUserStore } from '@/store'
import { useGroupTypeStore } from '@/store/groupType'
import downDep from '@/static/image/chatList/downDep.png'
import downDepDis from '@/static/image/chatList/downDepDis.png'
import checkBox from '@/components/checkBox/index.vue'
import lodash from 'lodash'
const {
@ -197,201 +226,204 @@ const {
crumbs,
crumbsIndex,
depCheckedKeys,
} = useGroupTypeStore();
} = useGroupTypeStore()
const userStore = useUserStore()
const searchVal = ref("");
const crumbsContainer = ref(null);
const showWin = ref(false);
const searchVal = ref('')
const crumbsContainer = ref(null)
const showWin = ref(false)
const getAllCheckedNodes = (node, checkedNodes = []) => {
if (node.checkStatus === 'checked') {
checkedNodes.push(node);
checkedNodes.push(node)
}
if (node.sons && Array.isArray(node.sons)) {
node.sons.forEach(son => getAllCheckedNodes(son, checkedNodes));
node.sons.forEach((son) => getAllCheckedNodes(son, checkedNodes))
}
return checkedNodes;
};
return checkedNodes
}
const userDepIds = computed(() => {
return userStore.deps.map(v=>v.dept_id);
});
return userStore.deps.map((v) => v.dept_id)
})
const allCheckedList = computed(() => {
const checkedNodes = [];
depTreeMyList.value.forEach(node => getAllCheckedNodes(node, checkedNodes));
console.log("checkedNodes", checkedNodes);
const checkedNodes = []
depTreeMyList.value.forEach((node) => getAllCheckedNodes(node, checkedNodes))
console.log('checkedNodes', checkedNodes)
return checkedNodes;
return checkedNodes
})
const currentCrumbs = computed(() => {
if (crumbs.value[crumbsIndex.value] ) {
if (searchVal.value && searchVal.value !== "") {
if (crumbs.value[crumbsIndex.value]) {
if (searchVal.value && searchVal.value !== '') {
let filterSons = crumbs.value[crumbsIndex.value].sons.filter((item) =>
item.name.includes(searchVal.value)
);
item.name.includes(searchVal.value),
)
return {
...crumbs.value[crumbsIndex.value],
sons: filterSons,
};
}
console.log("allCheckedList", crumbs.value[crumbsIndex.value]);
return crumbs.value[crumbsIndex.value];
}
console.log('allCheckedList', crumbs.value[crumbsIndex.value])
return crumbs.value[crumbsIndex.value]
}
return {}
});
})
const allCheckStatus = computed(() => {
if (!currentCrumbs.value.sons) {
return "noChecked";
return 'noChecked'
}
const allChecked = currentCrumbs.value.sons.every((son) => son.checkStatus === "checked");
const someChecked =currentCrumbs.value.sons.some(
(son) =>
son.checkStatus === "checked" || son.checkStatus === "halfChecked"
);
const allChecked = currentCrumbs.value.sons.every(
(son) => son.checkStatus === 'checked',
)
const someChecked = currentCrumbs.value.sons.some(
(son) => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
)
if (allChecked) {
return "checked";
return 'checked'
} else if (someChecked) {
return "halfChecked";
return 'halfChecked'
} else {
return "noChecked";
return 'noChecked'
}
})
const findNodeById = (node, targetId) => {
if (node.ID === targetId) {
return node;
return node
}
if (node.sons && Array.isArray(node.sons)) {
for (const son of node.sons) {
const found = findNodeById(son, targetId);
const found = findNodeById(son, targetId)
if (found) {
return found;
return found
}
}
}
return null;
};
return null
}
const findParentNode = (node, targetId) => {
if (!node.sons || !Array.isArray(node.sons)) return null;
if (!node.sons || !Array.isArray(node.sons)) return null
for (const son of node.sons) {
if (son.ID === targetId) {
return node;
return node
}
const parent = findParentNode(son, targetId);
const parent = findParentNode(son, targetId)
if (parent) {
return parent;
return parent
}
}
return null;
};
return null
}
//
const updateParentStatus = (node) => {
const parent = findParentNode(depTreeMyList.value[0], node.ID);
if (!parent) return;
if (parent.checkStatus !== "checked"){
const parent = findParentNode(depTreeMyList.value[0], node.ID)
if (!parent) return
if (parent.checkStatus !== 'checked') {
const allChecked =
parent.sons &&
Array.isArray(parent.sons) &&
parent.sons.every((son) => son.checkStatus === "checked");
parent.sons.every((son) => son.checkStatus === 'checked')
const someChecked =
parent.sons &&
Array.isArray(parent.sons) &&
parent.sons.some(
(son) =>
son.checkStatus === "checked" || son.checkStatus === "halfChecked"
);
son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
)
if (allChecked) {
parent.checkStatus = "halfChecked";
parent.checkStatus = 'halfChecked'
} else if (someChecked) {
parent.checkStatus = "halfChecked";
parent.checkStatus = 'halfChecked'
} else {
parent.checkStatus = "noChecked";
parent.checkStatus = 'noChecked'
}
}
updateParentStatus(parent);
};
updateParentStatus(parent)
}
//
const updateNodeStatus = (node) => {
if (!node.sons || !Array.isArray(node.sons)) return;
const allChecked = node.sons.every(son => son.checkStatus === 'checked');
const someChecked = node.sons.some(son => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked');
if (!node.sons || !Array.isArray(node.sons)) return
const allChecked = node.sons.every((son) => son.checkStatus === 'checked')
const someChecked = node.sons.some(
(son) => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
)
if (allChecked) {
node.checkStatus = 'halfChecked';
node.checkStatus = 'halfChecked'
} else if (someChecked) {
node.checkStatus = 'halfChecked';
node.checkStatus = 'halfChecked'
} else {
node.checkStatus = 'noChecked';
node.checkStatus = 'noChecked'
}
};
}
//
const updateCheckStatus = (node, status) => {
node.checkStatus = status;
node.checkStatus = status
if (node.sons && Array.isArray(node.sons) && node.sons.length > 0) {
node.sons.forEach((son) => updateCheckStatus(son, status));
node.sons.forEach((son) => updateCheckStatus(son, status))
}
};
}
const checkItemChange = (item, val) => {
//
updateCheckStatus(item, val);
updateCheckStatus(item, val)
//
updateParentStatus(item);
};
updateParentStatus(item)
}
const deleteNode = (item) => {
const treeNode = findNodeById(depTreeMyList.value[0], item.ID);
if (!treeNode) return;
treeNode.checkStatus = "noChecked";
updateNodeStatus(treeNode);
updateParentStatus(treeNode);
};
const treeNode = findNodeById(depTreeMyList.value[0], item.ID)
if (!treeNode) return
treeNode.checkStatus = 'noChecked'
updateNodeStatus(treeNode)
updateParentStatus(treeNode)
}
const toNextLevel = async (item) => {
if (item.checkStatus !== "checked") {
crumbs.value.push(item);
crumbsIndex.value++;
await nextTick();
if (item.checkStatus !== 'checked') {
crumbs.value.push(item)
crumbsIndex.value++
await nextTick()
if (crumbsContainer.value) {
crumbsContainer.value.scrollLeft = crumbsContainer.value.scrollWidth;
crumbsContainer.value.scrollLeft = crumbsContainer.value.scrollWidth
}
}
};
}
const handleCrumbsClick = (index) => {
crumbsIndex.value = index;
crumbs.value = crumbs.value.slice(0, index + 1);
};
crumbsIndex.value = index
crumbs.value = crumbs.value.slice(0, index + 1)
}
const allCheck = (status) => {
let statusT = 'noChecked';
if (status === "checked") {
statusT = "noChecked";
let statusT = 'noChecked'
if (status === 'checked') {
statusT = 'noChecked'
} else {
statusT = "checked";
statusT = 'checked'
}
currentCrumbs.value.sons.forEach((item) => {
const itemT = findNodeById(depTreeMyList.value[0], item.ID)
if (!itemT) return;
checkItemChange(itemT, statusT);
});
};
if (!itemT) return
checkItemChange(itemT, statusT)
})
}
const openDrawer = () => {
showWin.value = true;
if (allCheckedList.length>0) {
showWin.value = true
if (allCheckedList.length > 0) {
}
};
}
// watch(() => depTreeMyList.value, (newValue, oldValue) => {
// console.log("depTreeMyList", newValue);
@ -404,54 +436,48 @@ const openDrawer = () => {
const handleConfirm = () => {
depCheckedKeys.value = lodash.cloneDeep(allCheckedList.value)
console.log("depCheckedKeys", depCheckedKeys.value);
console.log('depCheckedKeys', depCheckedKeys.value)
uni.navigateBack();
uni.navigateBack()
}
const initCheckedKeys = () => {
depCheckedKeys.value.forEach((item) => {
const node = findNodeById(depTreeMyList.value[0], item.ID);
const node = findNodeById(depTreeMyList.value[0], item.ID)
if (node) {
node.checkStatus = "checked";
updateParentStatus(node);
node.checkStatus = 'checked'
updateParentStatus(node)
}
});
};
})
}
const init = async () => {
crumbsIndex.value = 0;
await getDepsTreeMy();
crumbs.value = depTreeMyList.value.length ? [depTreeMyList.value[0]] : [];
initCheckedKeys();
};
crumbsIndex.value = 0
await getDepsTreeMy()
crumbs.value = depTreeMyList.value.length ? [depTreeMyList.value[0]] : []
initCheckedKeys()
}
onMounted(() => {
init();
});
init()
})
</script>
<style scoped lang="scss">
uni-page-body,
page {
::v-deep .zp-paging-container-content {
height: 100%;
display: flex;
}
.outer-layer {
overflow-y: auto;
.choose-deps-page {
.choose-deps {
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
display: flex;
flex-direction: column;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
padding-bottom: 0;
display: flex;
flex-direction: column;
}
.root {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-top: 18rpx;
overflow: hidden;
padding: 20rpx 0;
}
}
.divider {
height: 1rpx;
background-color: #707070;
@ -478,22 +504,22 @@ page {
background-repeat: no-repeat;
border-radius: 12rpx;
&.firstPanel {
background-image: url("@/static/image/chatList/zu6033@2x.png");
background-image: url('@/static/image/chatList/zu6033@2x.png');
}
&.secondPanel {
background-image: url("@/static/image/chatList/zu6031@2x.png");
background-image: url('@/static/image/chatList/zu6031@2x.png');
margin-top: 28rpx;
margin-bottom: 28rpx;
}
&.thirdPanel {
background-image: url("@/static/image/chatList/zu6032@2x.png");
background-image: url('@/static/image/chatList/zu6032@2x.png');
}
&.activePanel {
box-shadow: 0 0 0 3rpx #46299d;
}
}
.btnBox {
:deep(uni-button[disabled="true"]) {
:deep(uni-button[disabled='true']) {
color: #bebebe !important;
}
}
@ -508,12 +534,12 @@ page {
.diyBtn {
:deep(uni-button) {
color: #191919 !important;
border: 1rpx solid #D6D6D8 !important;
background-color: #FFFFFF !important;
&[disabled="true"] {
color: #BEBEBE !important;
border: 1rpx solid #E6E6E6 !important;
background-color: #E6E6E6 !important;
border: 1rpx solid #d6d6d8 !important;
background-color: #ffffff !important;
&[disabled='true'] {
color: #bebebe !important;
border: 1rpx solid #e6e6e6 !important;
background-color: #e6e6e6 !important;
}
}
}

View File

@ -1,159 +1,195 @@
<template>
<div class="outer-layer">
<div>
<tm-navbar :hideBack="false" hideHome title="选择群类型">
</tm-navbar>
</div>
<div class="root">
<div class="w-full h-[1134rpx] mb-[20rpx] pl-[32rpx] pr-[32rpx] overflow-y-auto" >
<div class=" pl-[32rpx] pr-[32rpx] pt-[44rpx] pb-[42rpx] bg-[#FFFFFF]" >
<div class="text-[40rpx] leading-[54rpx] text-[#2F2F2F] font-bold" >
<div class="choose-group-type-page">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<customNavbar :title="$t('pageTitle.select.groupType')"></customNavbar>
</template>
<div class="choose-group-type">
<div class="w-full h-[1134rpx] mb-[20rpx] pl-[32rpx] pr-[32rpx]">
<div class="pl-[32rpx] pr-[32rpx] pt-[44rpx] pb-[42rpx] bg-[#FFFFFF]">
<div class="text-[40rpx] leading-[54rpx] text-[#2F2F2F] font-bold">
群类型保存后将不可修改
</div>
<div class="text-[28rpx] leading-[54rpx] text-[#46299D] mt-[16rpx]" >
<div class="text-[28rpx] leading-[54rpx] text-[#46299D] mt-[16rpx]">
请创建过程中正确选择
</div>
<div class="mt-[54rpx] w-full h-[872rpx]" >
<div @click="groupActiveIndex = 0" class="groupCard firstPanel" :class="groupActiveIndex === 0?'activePanel':''">
<div class="mt-[54rpx] w-full h-[872rpx]">
<div
@click="groupActiveIndex = 0"
class="groupCard firstPanel"
:class="groupActiveIndex === 0 ? 'activePanel' : ''"
>
<div class="w-full h-full pt-[64rpx] pl-[36rpx]">
<div class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold" >
<div
class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold"
>
普通群
</div>
<div class="text-[24rpx] leading-[36rpx] text-[#939393] w-[216rpx]" >
员工线上沟通专用群
离职后自动退群
<div
class="text-[24rpx] leading-[36rpx] text-[#939393] w-[216rpx]"
>
员工线上沟通专用群 离职后自动退群
</div>
</div>
</div>
<div @click="groupActiveIndex = 1" class="groupCard secondPanel" :class="groupActiveIndex === 1?'activePanel':''">
<div class="w-full h-full pr-[36rpx] flex flex-col items-end justify-center">
<div class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold" >
<div
@click="groupActiveIndex = 1"
class="groupCard secondPanel"
:class="groupActiveIndex === 1 ? 'activePanel' : ''"
>
<div
class="w-full h-full pr-[36rpx] flex flex-col items-end justify-center"
>
<div
class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold"
>
部门群
</div>
<div class="text-[24rpx] leading-[36rpx] text-[#939393] w-[288rpx] text-end" >
指定部门员工入职自动进群
离职后自动退群
<div
class="text-[24rpx] leading-[36rpx] text-[#939393] w-[288rpx] text-end"
>
指定部门员工入职自动进群 离职后自动退群
</div>
<div
v-if="!depCheckedKeys.length"
@click="chooseDep"
class="text-[24rpx] leading-[36rpx] text-[#C1B4EA] flex items-center mt-[16rpx]"
>
<div
:class="
groupActiveIndex === 1
? 'text-[#7A58DE]'
: 'text-[#C1B4EA]'
"
>
选择部门
</div>
<div v-if="!depCheckedKeys.length" @click="chooseDep" class="text-[24rpx] leading-[36rpx] text-[#C1B4EA] flex items-center mt-[16rpx]" >
<div :class="groupActiveIndex === 1?'text-[#7A58DE]':'text-[#C1B4EA]'" >选择部门</div>
<div class="ml-[20rpx]">
<tm-icon name="tmicon-angle-right" :font-size="18" :color="groupActiveIndex === 1?'#7A58DE':'#C1B4EA'"></tm-icon>
<tm-icon
name="tmicon-angle-right"
:font-size="18"
:color="groupActiveIndex === 1 ? '#7A58DE' : '#C1B4EA'"
></tm-icon>
</div>
</div>
<div v-else class="text-[24rpx] leading-[36rpx] flex flex-col justify-center items-end mt-[16rpx] mb-[22rpx]" >
<div class="max-w-[336rpx] truncate" >
{{ depCheckedKeys.map(v=>v.name).toString() }}
<div
v-else
class="text-[24rpx] leading-[36rpx] flex flex-col justify-center items-end mt-[16rpx] mb-[22rpx]"
>
<div class="max-w-[336rpx] truncate">
{{ depCheckedKeys.map((v) => v.name).toString() }}
</div>
<div @click="chooseDep" class="flex items-center" >
<div class="text-[#7A58DE]" >查看全部</div>
<div @click="chooseDep" class="flex items-center">
<div class="text-[#7A58DE]">查看全部</div>
<div class="ml-[20rpx]">
<tm-icon name="tmicon-angle-right" :font-size="18" :color="groupActiveIndex === 1?'#7A58DE':'#C1B4EA'"></tm-icon>
<tm-icon
name="tmicon-angle-right"
:font-size="18"
:color="
groupActiveIndex === 1 ? '#7A58DE' : '#C1B4EA'
"
></tm-icon>
</div>
</div>
</div>
</div>
</div>
<div @click="groupActiveIndex = 2" class="groupCard thirdPanel" :class="groupActiveIndex === 2?'activePanel':''">
<div
@click="groupActiveIndex = 2"
class="groupCard thirdPanel"
:class="groupActiveIndex === 2 ? 'activePanel' : ''"
>
<div class="w-full h-full pt-[64rpx] pl-[36rpx]">
<div class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold" >
<div
class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold"
>
项目群
</div>
<div class="text-[24rpx] leading-[36rpx] text-[#939393] w-[216rpx]" >
项目成员沟通专用群
离职后自动退群
</div>
</div>
</div>
</div>
</div>
</div>
<div class="h-[162rpx] flex justify-center bg-[#FFFFFF]">
<div class="mt-[14rpx] btnBox" >
<tm-button
@click="handleConfirm"
color="#46299D"
:disabled="confirmBtnStatus"
disabledColor="#E6E6E6"
:margin="[0]"
:shadow="0"
:width="426"
:height="76"
size="large"
label="确定"
<div
class="text-[24rpx] leading-[36rpx] text-[#939393] w-[216rpx]"
>
</tm-button>
项目成员沟通专用群 离职后自动退群
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<template #bottom>
<customBtn
:isBottom="true"
:btnText="$t('ok')"
@click="handleConfirm"
:disabled="confirmBtnStatus"
></customBtn>
</template>
</ZPaging>
</div>
</template>
<script setup>
import { ref, watch, computed } from "vue";
import { onShow, onLoad } from "@dcloudio/uni-app";
import { useChatList } from "@/store/chatList/index.js";
import { useAuth } from "@/store/auth";
import { useTalkStore, useUserStore } from "@/store";
import { useGroupTypeStore } from "@/store/groupType";
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue'
import { ref, watch, computed } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import { useTalkStore, useUserStore } from '@/store'
import { useGroupTypeStore } from '@/store/groupType'
const { groupActiveIndex,depCheckedKeys } = useGroupTypeStore();
const { groupActiveIndex, depCheckedKeys } = useGroupTypeStore()
const confirmBtnStatus = computed(() => {
let disabledT = false;
let disabledT = false
switch (groupActiveIndex.value) {
case 0:
break;
break
case 1:
if (!depCheckedKeys.value.length) {
disabledT = true;
disabledT = true
}
break;
break
case 2:
break;
break
default:
break;
break
}
return disabledT
})
const chooseDep = () => {
uni.navigateTo({
url: '/pages/chooseDeps/index'
});
};
url: '/pages/chooseByDeps/index?chooseMode=1',
})
}
const handleConfirm = () => {
uni.navigateBack()
// uni.navigateTo({
// url: '/pages/creatGroupChat/index'
// });
};
}
</script>
<style scoped lang="scss">
uni-page-body,
page {
::v-deep .zp-paging-container-content {
height: 100%;
display: flex;
}
.outer-layer {
overflow-y: auto;
.choose-group-type {
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
display: flex;
flex-direction: column;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
padding-bottom: 0;
display: flex;
flex-direction: column;
background-position: bottom center;
padding: 20rpx 0;
background-attachment: fixed;
}
.root {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-top: 18rpx;
overflow: hidden;
}
.divider{
.divider {
height: 1rpx;
background-color: #7C7C7C;
background-color: #7c7c7c;
opacity: 0.6;
}
.avatar-placeholder {
@ -171,24 +207,23 @@ page {
background-repeat: no-repeat;
border-radius: 12rpx;
&.firstPanel {
background-image: url("@/static/image/chatList/zu6033@2x.png");
background-image: url('@/static/image/chatList/zu6033@2x.png');
}
&.secondPanel {
background-image: url("@/static/image/chatList/zu6031@2x.png");
background-image: url('@/static/image/chatList/zu6031@2x.png');
margin-top: 28rpx;
margin-bottom: 28rpx;
}
&.thirdPanel {
background-image: url("@/static/image/chatList/zu6032@2x.png");
background-image: url('@/static/image/chatList/zu6032@2x.png');
}
&.activePanel {
box-shadow: 0 0 0 3rpx #46299D;
box-shadow: 0 0 0 3rpx #46299d;
}
}
.btnBox {
:deep(uni-button[disabled="true"]){
color: #BEBEBE !important;
:deep(uni-button[disabled='true']) {
color: #bebebe !important;
}
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div class="outer-layer">
<div>
<tm-navbar :hideBack="false" hideHome title="选择部门"> </tm-navbar>
<tm-navbar :hideBack="false" hideHome title="选择部门"></tm-navbar>
</div>
<div class="root">
<div
@ -48,7 +48,7 @@
</div>
<div
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
@click="()=>allCheck(allCheckStatus)"
@click="() => allCheck(allCheckStatus)"
>
<div>
<checkBox
@ -63,12 +63,20 @@
<div
v-if="currentCrumbs?.sons?.length"
v-for="item in currentCrumbs?.sons"
@click="
checkItemChange(
item,
item.checkStatus
? item.checkStatus === 'checked'
? 'noChecked'
: 'checked'
: 'checked',
)
"
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
>
<div class="w-full flex items-center justify-between">
<div
class="flex items-center"
>
<div class="flex items-center">
<div>
<checkBox
:disabled="!item?.sons?.length"
@ -82,7 +90,10 @@
</div>
<div v-if="item.sons?.length" class="flex items-center mr-[32rpx]">
<div class="vDivider mr-[32rpx]"></div>
<div @click="() => toNextLevel(item)" class="flex items-center">
<div
@click.stop="() => toNextLevel(item)"
class="flex items-center"
>
<div class="mr-[12rpx]">
<tm-image
:width="26"
@ -108,15 +119,26 @@
<div
v-if="currentMembers.length"
v-for="item in currentMembers"
@click="
checkMember(
item,
membersCheckedKeys.filter((v) => v.ID === item.ID).length > 0
? 'noChecked'
: 'checked',
)
"
class="pl-[32rpx] pr-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
>
<div class="w-full flex items-center justify-between">
<div
class="flex items-center"
>
<div class="flex items-center">
<div>
<checkBox
:modelValue="membersCheckedKeys.filter(v=>v.ID === item.ID).length>0?'checked':'noChecked'"
:modelValue="
membersCheckedKeys.filter((v) => v.ID === item.ID).length >
0
? 'checked'
: 'noChecked'
"
@change="(val) => checkMember(item, val)"
></checkBox>
</div>
@ -124,24 +146,30 @@
<div class="userAvatar flex items-center justify-center">
{{ item.nickName.slice(-2) }}
</div>
<div class="ml-[20rpx] flex flex-col items-center">
<div class="text-[28rpx] font-bold" >{{ item.nickName }}</div>
<div class="text-[20rpx] text-[#747474]" >{{ item.jobNum }}</div>
<div class="ml-[20rpx] flex flex-col justify-center">
<div class="text-[28rpx] font-bold">{{ item.nickName }}</div>
<div class="text-[20rpx] text-[#747474]">
{{ item.jobNum }}
</div>
</div>
<tm-popover position="tc">
<div class="ml-[20rpx] flex h-[68rpx] flex-wrap line-clamp-2 max-w-[342rpx]">
<div
class="ml-[6rpx] flex h-[68rpx] flex-wrap line-clamp-2 max-w-[342rpx]"
>
<div
v-for="post in item.positions"
class="postTag truncate mb-[4rpx] mr-[14rpx] max-w-[164rpx]"
class="postTag truncate mb-[4rpx] ml-[14rpx] max-w-[164rpx]"
>
{{ post.name }}
</div>
</div>
<template v-slot:label>
<div class="max-h-[250rpx] overflow-y-auto pt-[10rpx] pl-[18rpx] pr-[18rpx] pb-[12rpx]">
<div
class="max-h-[250rpx] overflow-y-auto pt-[10rpx] pl-[18rpx] pr-[18rpx] pb-[12rpx]"
>
<div
v-for="post in item.positions"
class="postTag truncate mb-[10rpx] "
class="postTag truncate mb-[10rpx]"
>
{{ post.name }}
</div>
@ -152,25 +180,33 @@
</div>
</div>
</div>
</div>
<div class="h-[162rpx] pl-[32rpx] pr-[32rpx] bg-[#FFFFFF]">
<div class="mt-[14rpx] flex justify-between">
<div class="flex flex-col">
<div @click="openDrawer" class="flex items-center text-[28rpx] leading-[60rpx] text-[#000000] font-bold">
<div
@click="openDrawer"
class="flex items-center text-[28rpx] leading-[60rpx] text-[#000000] font-bold"
>
<div>已选择</div>
<div>{{ totalMembers }}</div>
<div class="ml-[28rpx]">
<tm-icon :fontSize="24" color="#46299D" name="tmicon-angle-up"></tm-icon>
<tm-icon
:fontSize="24"
color="#46299D"
name="tmicon-angle-up"
></tm-icon>
</div>
</div>
<div class="text-[24rpx] leading-[24rpx] text-[#7A58DE] w-[280rpx] truncate ">
<span v-for="item in allCheckedList" >
<div
class="text-[24rpx] leading-[24rpx] text-[#7A58DE] w-[280rpx] truncate"
>
<span v-for="item in allCheckedList">
{{ item.name }}({{ item.staffNum }})
</span>
<span v-for="(item,index) in membersCheckedKeys" >
<span v-for="(item, index) in membersCheckedKeys">
{{ item.nickName }}
{{ index!== membersCheckedKeys.length-1?',':"" }}
{{ index !== membersCheckedKeys.length - 1 ? ',' : '' }}
</span>
</div>
</div>
@ -186,8 +222,7 @@
:height="76"
size="large"
label="确定"
>
</tm-button>
></tm-button>
</div>
</div>
</div>
@ -199,48 +234,63 @@
:hideHeader="true"
:round="5"
>
<div class="flex flex-col w-full h-full pt-[36rpx] pl-[32rpx] pr-[32rpx] leading-[60rpx]" >
<div class="text-[32rpx] font-bold flex items-center justify-between" >
<div
class="flex flex-col w-full h-full pt-[36rpx] pl-[32rpx] pr-[32rpx] leading-[60rpx]"
>
<div class="text-[32rpx] font-bold flex items-center justify-between">
<div class="flex items-center ml-[10rpx]">
<div>已选择部门数</div>
<div>{{ allCheckedList.length }}</div>
</div>
<div
class="text-[#7A58DE] mr-[10rpx]"
@click="()=>showWin = false"
@click="() => (showWin = false)"
>
确定
</div>
</div>
<div class="flex-1 pb-[20rpx] overflow-y-auto pt-[30rpx]" >
<div
v-for="(item,index) in allCheckedList"
class="flex flex-col"
>
<div v-if="index ===0" class="divider" ></div>
<div class="flex-1 pb-[20rpx] overflow-y-auto pt-[30rpx]">
<div v-for="(item, index) in allCheckedList" class="flex flex-col">
<div v-if="index === 0" class="divider"></div>
<div
class="flex items-center justify-between mt-[36rpx] font-bold text-[#000000] leading-[54rpx] mb-[34rpx]"
>
<div class="text-[28rpx] ml-[10rpx]">{{ item.name }}{{ item.staffNum }}</div>
<div class="text-[28rpx] ml-[10rpx]">
{{ item.name }}{{ item.staffNum }}
</div>
<div class="diyBtn">
<tm-button @click="()=>deleteNode(item)" :disabled="userDepIds.includes(item.ID)" :margin="[10]" :shadow="0" text size="small" :width="106" :height="50" outlined label="移除"></tm-button>
<tm-button
@click="() => deleteNode(item)"
:disabled="userDepIds.includes(item.ID)"
:margin="[10]"
:shadow="0"
text
size="small"
:width="106"
:height="50"
outlined
label="移除"
></tm-button>
</div>
</div>
<div class="divider" ></div>
<div class="divider"></div>
</div>
<div
v-for="(item,index) in membersCheckedKeys"
v-for="(item, index) in membersCheckedKeys"
class="flex flex-col"
>
<div v-if="index ===0 && allCheckedList.length==0" class="divider" ></div>
<div
v-if="index === 0 && allCheckedList.length == 0"
class="divider"
></div>
<div
class="flex items-center justify-between mt-[36rpx] font-bold text-[#000000] leading-[54rpx] mb-[34rpx]"
>
<div class="text-[28rpx] ml-[10rpx] flex">
<div>
{{ item.nickName }}({{ item.jobNum }})
</div>
<div class="ml-[20rpx] flex h-[68rpx] flex-wrap line-clamp-2 max-w-[342rpx]">
<div>{{ item.nickName }}({{ item.jobNum }})</div>
<div
class="ml-[20rpx] flex h-[68rpx] flex-wrap line-clamp-2 max-w-[342rpx]"
>
<div
v-for="post in item.positions"
class="postTag truncate mb-[4rpx] mr-[14rpx] max-w-[164rpx]"
@ -250,10 +300,20 @@
</div>
</div>
<div class="diyBtn">
<tm-button @click="()=>deleteMember(item)" :margin="[10]" :shadow="0" text size="small" :width="106" :height="50" outlined label="移除"></tm-button>
<tm-button
@click="() => deleteMember(item)"
:margin="[10]"
:shadow="0"
text
size="small"
:width="106"
:height="50"
outlined
label="移除"
></tm-button>
</div>
</div>
<div class="divider" ></div>
<div class="divider"></div>
</div>
</div>
</div>
@ -261,15 +321,15 @@
</div>
</template>
<script setup>
import { ref, watch, computed, onMounted,nextTick } from "vue";
import { onShow, onLoad } from "@dcloudio/uni-app";
import { useChatList } from "@/store/chatList/index.js";
import { useAuth } from "@/store/auth";
import { useTalkStore, useUserStore } from "@/store";
import { useGroupTypeStore } from "@/store/groupType";
import downDep from "@/static/image/chatList/downDep.png";
import downDepDis from "@/static/image/chatList/downDepDis.png";
import checkBox from "@/components/checkBox/index.vue";
import { ref, watch, computed, onMounted, nextTick } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import { useTalkStore, useUserStore } from '@/store'
import { useGroupTypeStore } from '@/store/groupType'
import downDep from '@/static/image/chatList/downDep.png'
import downDepDis from '@/static/image/chatList/downDepDis.png'
import checkBox from '@/components/checkBox/index.vue'
import lodash from 'lodash'
const {
@ -282,76 +342,78 @@ const {
getDepMembers,
membersCheckedKeys,
allChooseMembers,
} = useGroupTypeStore();
} = useGroupTypeStore()
const userStore = useUserStore()
const searchVal = ref("");
const crumbsContainer = ref(null);
const showWin = ref(false);
const searchVal = ref('')
const crumbsContainer = ref(null)
const showWin = ref(false)
const currentMembers = ref([])
const getAllCheckedNodes = (node, checkedNodes = []) => {
if (node.checkStatus === 'checked') {
checkedNodes.push(node);
checkedNodes.push(node)
}
if (node.sons && Array.isArray(node.sons)) {
node.sons.forEach(son => getAllCheckedNodes(son, checkedNodes));
node.sons.forEach((son) => getAllCheckedNodes(son, checkedNodes))
}
return checkedNodes;
};
return checkedNodes
}
const userDepIds = computed(() => {
return userStore.deps.map(v=>v.dept_id);
});
return userStore.deps.map((v) => v.dept_id)
})
const calculateTotalStaffNum = (node) => {
let total = node.staffNum || 0;
let total = node.staffNum || 0
if (node.sons && Array.isArray(node.sons)) {
node.sons.forEach(son => {
total += calculateTotalStaffNum(son);
});
node.sons.forEach((son) => {
total += calculateTotalStaffNum(son)
})
}
return total;
};
return total
}
const allCheckedList = computed(() => {
const checkedNodes = [];
depTreeMyList.value.forEach(node => getAllCheckedNodes(node, checkedNodes));
console.log("checkedNodes", checkedNodes);
const checkedNodes = []
depTreeMyList.value.forEach((node) => getAllCheckedNodes(node, checkedNodes))
console.log('checkedNodes', checkedNodes)
return checkedNodes;
return checkedNodes
})
const currentCrumbs = computed(() => {
if (crumbs.value[crumbsIndex.value] ) {
if (searchVal.value && searchVal.value !== "") {
if (crumbs.value[crumbsIndex.value]) {
if (searchVal.value && searchVal.value !== '') {
let filterSons = crumbs.value[crumbsIndex.value].sons.filter((item) =>
item.name.includes(searchVal.value)
);
item.name.includes(searchVal.value),
)
return {
...crumbs.value[crumbsIndex.value],
sons: filterSons,
};
}
console.log("allCheckedList", crumbs.value[crumbsIndex.value]);
return crumbs.value[crumbsIndex.value];
}
console.log('allCheckedList', crumbs.value[crumbsIndex.value])
return crumbs.value[crumbsIndex.value]
}
return {}
});
})
const getCurrentMembers = async (depItem) => {
const res = await getDepMembers({
departmentId:depItem.ID,
status:'notactive'
departmentId: depItem.ID,
status: 'notactive',
})
if (res.status === 0) {
currentMembers.value = res.data.data.length?res.data.data.map(v=>{
currentMembers.value = res.data.data.length
? res.data.data.map((v) => {
return {
...v,
isMember:true,
isMember: true,
}
}):[]
}else{
})
: []
} else {
currentMembers.value = []
}
}
@ -359,168 +421,178 @@ const getCurrentMembers = async (depItem) => {
const checkMember = (item, val) => {
if (val === 'checked') {
membersCheckedKeys.value.push(item)
}else {
membersCheckedKeys.value = membersCheckedKeys.value.filter(v=>v.ID !==item.ID)
} else {
membersCheckedKeys.value = membersCheckedKeys.value.filter(
(v) => v.ID !== item.ID,
)
}
}
watch(() => currentCrumbs.value, (newValue, oldValue) => {
watch(
() => currentCrumbs.value,
(newValue, oldValue) => {
getCurrentMembers(newValue)
})
},
)
const allCheckStatus = computed(() => {
if (!currentCrumbs.value.sons) {
return "noChecked";
return 'noChecked'
}
const allChecked = currentCrumbs.value.sons.every((son) => son.checkStatus === "checked");
const someChecked =currentCrumbs.value.sons.some(
(son) =>
son.checkStatus === "checked" || son.checkStatus === "halfChecked"
);
const allChecked = currentCrumbs.value.sons.every(
(son) => son.checkStatus === 'checked',
)
const someChecked = currentCrumbs.value.sons.some(
(son) => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
)
if (allChecked) {
return "checked";
return 'checked'
} else if (someChecked) {
return "halfChecked";
return 'halfChecked'
} else {
return "noChecked";
return 'noChecked'
}
})
const findNodeById = (node, targetId) => {
if (node.ID === targetId) {
return node;
return node
}
if (node.sons && Array.isArray(node.sons)) {
for (const son of node.sons) {
const found = findNodeById(son, targetId);
const found = findNodeById(son, targetId)
if (found) {
return found;
return found
}
}
}
return null;
};
return null
}
const findParentNode = (node, targetId) => {
if (!node.sons || !Array.isArray(node.sons)) return null;
if (!node.sons || !Array.isArray(node.sons)) return null
for (const son of node.sons) {
if (son.ID === targetId) {
return node;
return node
}
const parent = findParentNode(son, targetId);
const parent = findParentNode(son, targetId)
if (parent) {
return parent;
return parent
}
}
return null;
};
return null
}
//
const updateParentStatus = (node) => {
const parent = findParentNode(depTreeMyList.value[0], node.ID);
if (!parent) return;
if (parent.checkStatus !== "checked"){
const parent = findParentNode(depTreeMyList.value[0], node.ID)
if (!parent) return
if (parent.checkStatus !== 'checked') {
const allChecked =
parent.sons &&
Array.isArray(parent.sons) &&
parent.sons.every((son) => son.checkStatus === "checked");
parent.sons.every((son) => son.checkStatus === 'checked')
const someChecked =
parent.sons &&
Array.isArray(parent.sons) &&
parent.sons.some(
(son) =>
son.checkStatus === "checked" || son.checkStatus === "halfChecked"
);
son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
)
if (allChecked) {
parent.checkStatus = "halfChecked";
parent.checkStatus = 'halfChecked'
} else if (someChecked) {
parent.checkStatus = "halfChecked";
parent.checkStatus = 'halfChecked'
} else {
parent.checkStatus = "noChecked";
parent.checkStatus = 'noChecked'
}
}
updateParentStatus(parent);
};
updateParentStatus(parent)
}
//
const updateNodeStatus = (node) => {
if (!node.sons || !Array.isArray(node.sons)) return;
const allChecked = node.sons.every(son => son.checkStatus === 'checked');
const someChecked = node.sons.some(son => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked');
if (!node.sons || !Array.isArray(node.sons)) return
const allChecked = node.sons.every((son) => son.checkStatus === 'checked')
const someChecked = node.sons.some(
(son) => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
)
if (allChecked) {
node.checkStatus = 'halfChecked';
node.checkStatus = 'halfChecked'
} else if (someChecked) {
node.checkStatus = 'halfChecked';
node.checkStatus = 'halfChecked'
} else {
node.checkStatus = 'noChecked';
node.checkStatus = 'noChecked'
}
};
}
//
const updateCheckStatus = (node, status) => {
node.checkStatus = status;
node.checkStatus = status
if (node.sons && Array.isArray(node.sons) && node.sons.length > 0) {
node.sons.forEach((son) => updateCheckStatus(son, status));
node.sons.forEach((son) => updateCheckStatus(son, status))
}
};
}
const checkItemChange = (item, val) => {
//
updateCheckStatus(item, val);
updateCheckStatus(item, val)
//
updateParentStatus(item);
};
updateParentStatus(item)
}
const deleteNode = (item) => {
const treeNode = findNodeById(depTreeMyList.value[0], item.ID);
if (!treeNode) return;
treeNode.checkStatus = "noChecked";
updateNodeStatus(treeNode);
updateParentStatus(treeNode);
};
const treeNode = findNodeById(depTreeMyList.value[0], item.ID)
if (!treeNode) return
treeNode.checkStatus = 'noChecked'
updateNodeStatus(treeNode)
updateParentStatus(treeNode)
}
const deleteMember = (item) => {
membersCheckedKeys.value = membersCheckedKeys.value.filter(v=>v.ID !== item.ID)
membersCheckedKeys.value = membersCheckedKeys.value.filter(
(v) => v.ID !== item.ID,
)
}
const toNextLevel = async (item) => {
if (item.checkStatus !== "checked") {
crumbs.value.push(item);
crumbsIndex.value++;
await nextTick();
if (item.checkStatus !== 'checked') {
crumbs.value.push(item)
crumbsIndex.value++
await nextTick()
if (crumbsContainer.value) {
crumbsContainer.value.scrollLeft = crumbsContainer.value.scrollWidth;
crumbsContainer.value.scrollLeft = crumbsContainer.value.scrollWidth
}
}
};
}
const handleCrumbsClick = (index) => {
crumbsIndex.value = index;
crumbs.value = crumbs.value.slice(0, index + 1);
};
crumbsIndex.value = index
crumbs.value = crumbs.value.slice(0, index + 1)
}
const allCheck = (status) => {
let statusT = 'noChecked';
if (status === "checked") {
statusT = "noChecked";
let statusT = 'noChecked'
if (status === 'checked') {
statusT = 'noChecked'
} else {
statusT = "checked";
statusT = 'checked'
}
currentCrumbs.value.sons.forEach((item) => {
const itemT = findNodeById(depTreeMyList.value[0], item.ID)
if (!itemT) return;
checkItemChange(itemT, statusT);
});
};
if (!itemT) return
checkItemChange(itemT, statusT)
})
}
const openDrawer = () => {
showWin.value = true;
if (allCheckedList.length>0) {
showWin.value = true
if (allCheckedList.length > 0) {
}
};
}
// watch(() => depTreeMyList.value, (newValue, oldValue) => {
// console.log("depTreeMyList", newValue);
@ -531,59 +603,62 @@ const openDrawer = () => {
// console.log("searchVal", newValue);
// });
const getDepTotalMembers = (item) => {
const rootNode = depTreeMyList.value[0];
const targetNode = findNodeById(rootNode, item.ID);
const rootNode = depTreeMyList.value[0]
const targetNode = findNodeById(rootNode, item.ID)
if (targetNode) {
return calculateTotalStaffNum(targetNode);
return calculateTotalStaffNum(targetNode)
}
return 0;
return 0
}
const totalMembers = computed(() => {
const depMembers = allCheckedList.value.reduce((sum, item) => sum + item.staffNum, 0);
const memberCount = membersCheckedKeys.value.length;
return depMembers + memberCount;
const depMembers = allCheckedList.value.reduce(
(sum, item) => sum + item.staffNum,
0,
)
const memberCount = membersCheckedKeys.value.length
return depMembers + memberCount
})
const handleConfirm = async () => {
uni.showLoading()
const listT = membersCheckedKeys.value.map(v=>v)
const listT = membersCheckedKeys.value.map((v) => v)
const res = await getDepMembers({
departmentIds:allCheckedList.value.map(v=>v.ID),
status:'notactive',
departmentIds: allCheckedList.value.map((v) => v.ID),
status: 'notactive',
})
if (res.status == 0 && res.data?.data?.length) {
res.data?.data.forEach(v=>{
res.data?.data.forEach((v) => {
listT.push(v)
})
}
allChooseMembers.value = listT
depCheckedKeys.value = lodash.cloneDeep(allCheckedList.value)
uni.hideLoading()
uni.navigateBack();
uni.navigateBack()
}
const initCheckedKeys = () => {
depCheckedKeys.value.forEach((item) => {
const node = findNodeById(depTreeMyList.value[0], item.ID);
const node = findNodeById(depTreeMyList.value[0], item.ID)
if (node) {
node.checkStatus = "checked";
updateParentStatus(node);
node.checkStatus = 'checked'
updateParentStatus(node)
}
});
};
})
}
const init = async () => {
crumbsIndex.value = 0;
await getDepsTreeMy();
crumbs.value = depTreeMyList.value.length ? [depTreeMyList.value[0]] : [];
initCheckedKeys();
};
crumbsIndex.value = 0
await getDepsTreeMy()
crumbs.value = depTreeMyList.value.length ? [depTreeMyList.value[0]] : []
initCheckedKeys()
}
onMounted(() => {
init();
});
init()
})
</script>
<style scoped lang="scss">
uni-page-body,
@ -593,7 +668,7 @@ page {
.outer-layer {
overflow-y: auto;
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
padding-bottom: 0;
display: flex;
@ -633,22 +708,22 @@ page {
background-repeat: no-repeat;
border-radius: 12rpx;
&.firstPanel {
background-image: url("@/static/image/chatList/zu6033@2x.png");
background-image: url('@/static/image/chatList/zu6033@2x.png');
}
&.secondPanel {
background-image: url("@/static/image/chatList/zu6031@2x.png");
background-image: url('@/static/image/chatList/zu6031@2x.png');
margin-top: 28rpx;
margin-bottom: 28rpx;
}
&.thirdPanel {
background-image: url("@/static/image/chatList/zu6032@2x.png");
background-image: url('@/static/image/chatList/zu6032@2x.png');
}
&.activePanel {
box-shadow: 0 0 0 3rpx #46299d;
}
}
.btnBox {
:deep(uni-button[disabled="true"]) {
:deep(uni-button[disabled='true']) {
color: #bebebe !important;
}
}
@ -663,17 +738,17 @@ page {
.diyBtn {
:deep(uni-button) {
color: #191919 !important;
border: 1rpx solid #D6D6D8 !important;
background-color: #FFFFFF !important;
&[disabled="true"] {
color: #BEBEBE !important;
border: 1rpx solid #E6E6E6 !important;
background-color: #E6E6E6 !important;
border: 1rpx solid #d6d6d8 !important;
background-color: #ffffff !important;
&[disabled='true'] {
color: #bebebe !important;
border: 1rpx solid #e6e6e6 !important;
background-color: #e6e6e6 !important;
}
}
}
.userAvatar{
background: linear-gradient(#674BBC, #46299D);
.userAvatar {
background: linear-gradient(#674bbc, #46299d);
width: 72rpx;
height: 72rpx;
border-radius: 36rpx;
@ -681,12 +756,12 @@ page {
font-size: 24rpx;
font-weight: bold;
}
.postTag{
background-color: #EEE9F8;
.postTag {
background-color: #eee9f8;
height: 32rpx;
line-height: 32rpx;
font-size: 20rpx;
padding: 0 12rpx 0 12rpx;
color: #46299D;
color: #46299d;
}
</style>

View File

@ -1,29 +1,26 @@
<template>
<div class="outer-layer">
<div>
<tm-navbar :hideBack="false" hideHome title="发起群聊" :leftWidth="320">
</tm-navbar>
</div>
<div class="root">
<div class="w-full pl-[32rpx] pr-[32rpx] mb-[20rpx]" >
<div class="w-full mt-[60rpx] flex justify-center" >
<div v-if="groupActiveIndex === -1" class="avatar-placeholder">
</div>
<div class="mb-[40rpx]" v-else>
<tm-image
:width="192"
:height="192"
:round="12"
:src="avatarImg"
></tm-image>
<div class="create-group-chat-page">
<zPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<customNavbar :title="$t('pageTitle.create.group')"></customNavbar>
</template>
<div class="create-group-chat flex flex-col">
<div class="group-avatar flex items-center justify-center">
<div class="avatar-placeholder" v-if="groupActiveIndex === -1"></div>
<div v-else>
<avatarModule
:mode="2"
:avatar="avatarImg"
:groupType="groupType"
:customStyle="{ width: '192rpx', height: '192rpx' }"
></avatarModule>
</div>
</div>
<div class="input-group flex items-center justify-between" >
<div class="input-group flex items-center justify-between">
<div class="input-item">
群名称
</div>
<div class="input-box" >
<div class="input-box">
<tm-input
v-model="groupName"
:followTheme="false"
@ -35,274 +32,350 @@
:height="40"
:transprent="true"
placeholder="请输入群名称1~20个字"
:padding="[0,0]"
:padding="[0, 0]"
align="right"
></tm-input>
</div>
</div>
<div
class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]"
>
</tm-input>
</div>
</div>
<div class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]" >
<div class="flex items-center justify-between" >
<div class="flex items-center justify-between">
<div class="input-item">
群类型
</div>
<div @click="chooseGroupType" class="left-box" >
<div class="text-[#B4B4B4] text-[28rpx] font-bold" >
<span v-if="groupActiveIndex ===-1">请选择群类型</span>
<span v-else-if="groupActiveIndex ===0">普通群</span>
<span v-else-if="groupActiveIndex ===1">部门群</span>
<span v-else-if="groupActiveIndex ===2">项目群</span>
<div @click="chooseGroupType" class="left-box">
<div class="text-[#B4B4B4] text-[28rpx] font-bold">
<span v-if="groupActiveIndex === -1">请选择群类型</span>
<span v-else-if="groupActiveIndex === 0">普通群</span>
<span v-else-if="groupActiveIndex === 1">部门群</span>
<span v-else-if="groupActiveIndex === 2">项目群</span>
</div>
<div class="ml-[32rpx]" >
<tm-icon :font-size="22" color="#747474" name="tmicon-angle-right"></tm-icon>
<div class="ml-[32rpx]">
<tm-icon
:font-size="22"
color="#747474"
name="tmicon-angle-right"
></tm-icon>
</div>
</div>
</div>
<div v-if="depCheckedKeys.length" class="mt-[32rpx]" >
<div
v-for="(item,index) in depCheckedKeys"
v-if="depCheckedKeys.length && groupActiveIndex === 1"
class="mt-[32rpx]"
>
<div
v-for="(item, index) in depCheckedKeys"
class="text-[#747474] text-[28rpx] leading-[40rpx] font-bold"
:class="[
index !==0 ? 'mt-[10rpx]':'',
depsNoExpanded&&index>4 ? 'hidden':''
index !== 0 ? 'mt-[10rpx]' : '',
depsNoExpanded && index > 4 ? 'hidden' : '',
]"
>
{{ item.name }}
</div>
<div class="text-[#46299D] text-[28rpx] mt-[20rpx] font-bold flex justify-center" >
<div v-if="depCheckedKeys.length>5" @click="depsNoExpanded = !depsNoExpanded" class="w-[100rpx]">
{{ depsNoExpanded? '展开':'收起' }}
<div
class="text-[#46299D] text-[28rpx] mt-[20rpx] font-bold flex justify-center"
>
<div
v-if="depCheckedKeys.length > 5"
@click="depsNoExpanded = !depsNoExpanded"
class="w-[100rpx]"
>
{{ depsNoExpanded ? '展开' : '收起' }}
</div>
</div>
</div>
</div>
<div v-if="groupActiveIndex===0" class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]" >
<div class="flex items-center justify-between" >
<div
v-if="groupActiveIndex === 0 || groupActiveIndex === 2"
class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]"
>
<div class="flex items-center justify-between">
<div class="input-item">
群成员
</div>
<div @click="chooseMembers" class="left-box" >
<div class="ml-[32rpx] flex items-center" >
<div v-if="!groupAdmins.length" class="text-[#B4B4B4] text-[28rpx] font-bold mr-[32rpx]">全部({{ 0 }})</div>
<tm-icon :font-size="22" color="#747474" name="tmicon-angle-right"></tm-icon>
</div>
</div>
</div>
<div v-if="groupAdmins.length" class="mt-[32rpx]" >
<div @click="chooseMembers" class="left-box">
<div class="ml-[32rpx] flex items-center">
<div
v-for="(item,index) in groupAdmins"
class="text-[#747474] text-[28rpx] leading-[40rpx] font-bold"
:class="[
index !==0 ? 'mt-[10rpx]':'',
depsNoExpanded&&index>4 ? 'hidden':''
]"
v-if="!groupAdmins.length"
class="text-[#B4B4B4] text-[28rpx] font-bold mr-[32rpx]"
>
{{ item.name }}
全部({{ allChooseMembers?.length || 0 }})
</div>
<div class="text-[#46299D] text-[28rpx] mt-[20rpx] font-bold flex justify-center" >
<div v-if="groupAdmins.length>5" @click="depsNoExpanded = !depsNoExpanded" class="w-[100rpx]">
{{ depsNoExpanded? '展开':'收起' }}
<tm-icon
:font-size="22"
color="#747474"
name="tmicon-angle-right"
></tm-icon>
</div>
</div>
</div>
<groupMemberList
:groupType="3"
:is_manager="true"
:memberList="allChooseMembers"
:memberListsLimit="15"
:hideAddRemoveBtns="true"
></groupMemberList>
</div>
<div v-if="groupActiveIndex===1" class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]" >
<div class="flex items-center justify-between" >
<div
v-if="groupActiveIndex === 1"
class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]"
>
<div class="flex items-center justify-between">
<div class="input-item">
群管理员
</div>
<div @click="chooseGroupAdmin" class="left-box" >
<div class="ml-[32rpx] flex items-center" >
<div v-if="!groupAdmins.length" class="text-[#B4B4B4] text-[28rpx] font-bold mr-[32rpx]">请选择群管理员</div>
<tm-icon :font-size="22" color="#747474" name="tmicon-angle-right"></tm-icon>
</div>
</div>
</div>
<div v-if="groupAdmins.length" class="mt-[32rpx]" >
<div @click="chooseGroupAdmin" class="left-box">
<div class="ml-[32rpx] flex items-center">
<div
v-for="(item,index) in groupAdmins"
v-if="!groupAdmins.length"
class="text-[#B4B4B4] text-[28rpx] font-bold mr-[32rpx]"
>
请选择群管理员
</div>
<tm-icon
:font-size="22"
color="#747474"
name="tmicon-angle-right"
></tm-icon>
</div>
</div>
</div>
<div v-if="groupAdmins.length" class="mt-[32rpx]">
<div
v-for="(item, index) in groupAdmins"
class="text-[#747474] text-[28rpx] leading-[40rpx] font-bold"
:class="[
index !==0 ? 'mt-[10rpx]':'',
depsNoExpanded&&index>4 ? 'hidden':''
index !== 0 ? 'mt-[10rpx]' : '',
depsNoExpanded && index > 4 ? 'hidden' : '',
]"
>
{{ item.name }}
</div>
<div class="text-[#46299D] text-[28rpx] mt-[20rpx] font-bold flex justify-center" >
<div v-if="groupAdmins.length>5" @click="depsNoExpanded = !depsNoExpanded" class="w-[100rpx]">
{{ depsNoExpanded? '展开':'收起' }}
</div>
</div>
</div>
</div>
</div>
<div class="h-[162rpx] flex justify-center bg-[#FFFFFF]">
<div class="mt-[14rpx] btnBox" >
<tm-button
@click="handleConfirm"
color="#46299D"
:disabled="confirmBtnStatus"
disabledColor="#E6E6E6"
:margin="[0]"
:shadow="0"
:width="426"
:height="76"
size="large"
label="发起群聊"
<div
class="text-[#46299D] text-[28rpx] mt-[20rpx] font-bold flex justify-center"
>
</tm-button>
<div
v-if="groupAdmins.length > 5"
@click="depsNoExpanded = !depsNoExpanded"
class="w-[100rpx]"
>
{{ depsNoExpanded ? '展开' : '收起' }}
</div>
</div>
</div>
</div>
</div>
<template #bottom>
<customBtn
:isBottom="true"
:btnText="$t('pageTitle.create.group')"
@click="handleConfirm"
:disabled="confirmBtnStatus"
></customBtn>
</template>
</zPaging>
</div>
</template>
<script setup>
import { ref, watch, computed } from "vue";
import { onShow, onLoad } from "@dcloudio/uni-app";
import { useChatList } from "@/store/chatList/index.js";
import { useAuth } from "@/store/auth";
import { useTalkStore, useUserStore } from "@/store";
import addCircle from "@/static/image/chatList/addCircle.png";
import cahtPopover from "@/static/image/chatList/cahtPopover.png";
import zu4992 from "@/static/image/chatList/zu4992@2x.png";
import zu4991 from "@/static/image/chatList/zu4991@2x.png";
import zu4989 from "@/static/image/chatList/zu4989@2x.png";
import { useGroupTypeStore } from "@/store/groupType";
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue'
import groupMemberList from '../chatSettings/components/groupMembersList.vue'
import avatarModule from '@/components/avatar-module/index.vue'
const { groupName,groupActiveIndex,depCheckedKeys,groupAdmins,createDepGroup,resetGroupInfo } = useGroupTypeStore();
const talkStore = useTalkStore();
const userStore = useUserStore();
const { userInfo } = useAuth();
import { ref, watch, computed } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import { useTalkStore, useUserStore, useGroupStore } from '@/store'
import addCircle from '@/static/image/chatList/addCircle.png'
import cahtPopover from '@/static/image/chatList/cahtPopover.png'
import { ServeCreateGroup } from '@/api/group/index'
import { useGroupTypeStore } from '@/store/groupType'
const groupChatType = ref('');
const depsNoExpanded = ref(true);
const {
groupName,
groupActiveIndex,
depCheckedKeys,
groupAdmins,
createDepGroup,
resetGroupInfo,
allChooseMembers,
} = useGroupTypeStore()
const talkStore = useTalkStore()
const userStore = useUserStore()
const groupStore = useGroupStore()
const { userInfo } = useAuth()
const avatarImg = computed(() => {
let srcT = "";
switch (groupActiveIndex.value) {
case 0:
srcT = zu4992;
break;
case 1:
srcT = zu4989;
break;
case 2:
srcT = zu4991;
break;
default:
srcT = zu4992;
}
return srcT;
const groupChatType = ref('')
const depsNoExpanded = ref(true)
onLoad(()=> {
groupStore.$reset()
})
//
const groupType = computed(() => {
let group_type = ''
switch (groupActiveIndex.value) {
case 0:
group_type = 1
break
case 1:
group_type = 2
break
case 2:
group_type = 3
break
default:
group_type = ''
}
return group_type
})
//
const chooseGroupType = () => {
uni.navigateTo({
url: '/pages/chooseGroupType/index'
url: '/pages/chooseGroupType/index',
})
}
const chooseGroupAdmin = () => {
uni.navigateTo({
url: '/pages/chooseGroupAdmin/index'
url:
'/pages/chatSettings/groupManage/selectMembers?manageType=admin&isCreateDepGroup=1',
})
}
const chooseMembers = () => {
uni.navigateTo({
url: '/pages/chooseMembers/index'
url: '/pages/chooseByDeps/index?chooseMode=2',
})
}
//
const handleConfirm = async () => {
// console.log(allChooseMembers.value)
let erp_ids = ''
if (allChooseMembers?.value?.length > 0) {
allChooseMembers?.value?.forEach((ele) => {
if (!erp_ids) {
erp_ids = String(ele.ID)
} else {
erp_ids += ',' + ele.ID
}
})
}
if (groupActiveIndex.value === 0) {
}else if(groupActiveIndex.value === 1){
//
let params = {
avatar: '',
name: groupName.value,
erp_ids: erp_ids,
type: 1,
profile: '',
}
console.log(params)
const res = await ServeCreateGroup(params)
if (res.code === 200) {
resetGroupInfo()
uni.navigateBack()
}
} else if (groupActiveIndex.value === 1) {
//
const res = await createDepGroup()
if (res.code === 200) {
resetGroupInfo()
uni.navigateBack()
}
}else {
} else if (groupActiveIndex.value === 2) {
//
let params = {
avatar: '',
name: groupName.value,
erp_ids: erp_ids,
type: 3,
profile: '',
}
console.log(params)
const res = await ServeCreateGroup(params)
if (res.code === 200) {
resetGroupInfo()
uni.navigateBack()
}
} else {
}
}
//
const confirmBtnStatus = computed(() => {
let disabledT = false;
if (groupName.value === "" || !groupActiveIndex.value) {
return true;
let disabledT = false
console.log(groupActiveIndex.value !== -1)
if (
groupName.value === '' ||
(groupActiveIndex.value && groupActiveIndex.value === -1) ||
(!groupActiveIndex.value && groupActiveIndex.value !== 0)
) {
return true
}
switch (groupActiveIndex.value) {
case 0:
break;
break
case 1:
if (!depCheckedKeys.value.length) {
disabledT = true;
disabledT = true
}
if (!groupAdmins.value.length) {
disabledT = true;
disabledT = true
}
break;
break
case 2:
break;
break
default:
break;
break
}
return disabledT
})
onShow(() => {
depsNoExpanded.value = true;
depsNoExpanded.value = true
})
</script>
<style scoped lang="scss">
uni-page-body,
page {
::v-deep .zp-paging-container-content {
height: 100%;
display: flex;
}
.outer-layer {
overflow-y: auto;
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
.create-group-chat {
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
padding-bottom: 0;
display: flex;
flex-direction: column;
}
.root {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.divider{
height: 1rpx;
background-color: #7C7C7C;
opacity: 0.6;
}
.avatar-placeholder {
background-position: center bottom;
width: 100%;
padding: 0 32rpx 20rpx;
.group-avatar {
padding: 60rpx 0;
.avatar-placeholder {
width: 192rpx;
height: 192rpx;
background-color: #e0e0e0;
border-radius: 50%;
margin-bottom: 40rpx;
}
}
}
.divider {
height: 1rpx;
background-color: #7c7c7c;
opacity: 0.6;
}
.input-group {
background-color: #fff;
padding-top: 38rpx;
padding-bottom: 32rpx;
padding-left: 32rpx;
padding-right: 40rpx;
// display: flex;
// align-items: center;
// justify-content: space-between;
padding: 38rpx 40rpx 32rpx 32rpx;
}
.input-item {
line-height: 40rpx;
@ -320,9 +393,4 @@ page {
display: flex;
align-items: center;
}
.btnBox {
:deep(uni-button[disabled="true"]) {
color: #bebebe !important;
}
}
</style>

View File

@ -3,13 +3,7 @@
<div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<tm-navbar
class="tmNavBar"
:hideBack="false"
hideHome
title=""
:leftWidth="220"
></tm-navbar>
<customNavbar class="tmNavBar"></customNavbar>
</template>
<div class="user-detail-info">
<div class="user-info-head user-info-card">
@ -66,6 +60,7 @@
:isBottom="true"
:btnText="$t('user.detail.sendMsg')"
:subBtnText="$t('user.detail.ringBell')"
@clickBtn="toTalkUser"
></customBtn>
</template>
</ZPaging>
@ -78,6 +73,9 @@ import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { onLoad } from '@dcloudio/uni-app'
import { reactive } from 'vue'
import { useTalkStore } from '@/store'
const talkStore = useTalkStore()
import { getUserInfoByClickAvatar } from '@/api/user/index'
import { useI18n } from 'vue-i18n'
@ -92,7 +90,7 @@ const state = reactive({
onLoad((options) => {
console.log(options)
if (options.erpUserId) {
state.erpUserId = options.erpUserId
state.erpUserId = Number(options.erpUserId)
getUserInfo()
}
})
@ -100,7 +98,7 @@ onLoad((options) => {
//
const getUserInfo = () => {
let params = {
erp_user_id: Number(state.erpUserId),
erp_user_id: state.erpUserId,
}
console.log(params)
const resp = getUserInfoByClickAvatar(params)
@ -172,6 +170,11 @@ const getUserInfo = () => {
resp.catch(() => {})
}
//
const toTalkUser = () => {
talkStore.toTalk(1, state.userInfo.sys_id, state.erpUserId)
}
</script>
<style scoped lang="scss">
.outer-layer {

View File

@ -1,25 +1,41 @@
<template>
<div class="outer-layer">
<div>
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
<div class="flex flex-col items-center justify-center">
<div class="text-[34rpx] font-bold">{{ talkParams.username }}</div>
<div v-if="true" class="text-[24rpx] text-[#999999]">公司群</div>
</div>
<template v-slot:right>
<div class="mr-[36rpx]">
<tm-icon color="rgb(51, 51, 51)" :font-size="36" name="tmicon-gengduo" @click="toChatSettingsPage"></tm-icon>
<div class="dialog-page">
<ZPaging
use-chat-record-mode
:refresher-enabled="false"
:show-scrollbar="false"
:loading-more-enabled="false"
:hide-empty-view="true"
height="100%"
ref="zpagingRef"
:use-virtual-list="true"
:preload-page="1"
cell-height-mode="dynamic"
virtual-scroll-fps="80"
:loading-more-custom-style="{ display: 'none', height: '0' }"
@virtualListChange="virtualListChange"
@scrolltolower="onRefreshLoad"
>
<template #top>
<customNavbar :title="talkParams.username">
<template
#subTitle
v-if="talkStore?.findItem(talkParams.index_name)?.group_type === 4"
>
<div class="text-[24rpx] text-[#999999]">公司群</div>
</template>
<template #right>
<div class="mr-[36rpx] toChatSetting_btn">
<tm-icon
color="rgb(51, 51, 51)"
:font-size="36"
name="tmicon-gengduo"
@click="toChatSettingsPage"
></tm-icon>
</div>
</template>
</tm-navbar>
</div>
<div class="root">
<div class="dialogBox">
<ZPaging :fixed="false" use-chat-record-mode :use-page-scroll="false" :refresher-enabled="false"
:show-scrollbar="false" :loading-more-enabled="false" :hide-empty-view="true" height="100%" ref="zpagingRef"
:use-virtual-list="true" :preload-page="1" cell-height-mode="dynamic" virtual-scroll-fps="80"
:loading-more-custom-style="{ display: 'none', height: '0' }" @virtualListChange="virtualListChange"
@scrolltolower="onRefreshLoad">
</customNavbar>
</template>
<!-- <template #top>
<div class="load-toolbar pointer">
<span v-if="loadConfig.status == 0"> 正在加载数据中 ... </span>
@ -29,55 +45,102 @@
</template> -->
<!-- 数据加载状态栏 -->
<div class="message-item" v-for="item in virtualList" :id="`zp-id-${item.zp_index}`" :key="item.zp_index"
style="transform: scaleY(-1);">
<div class="dialog-list">
<div
class="message-item"
v-for="item in virtualList"
:id="`zp-id-${item.zp_index}`"
:key="item.zp_index"
style="transform: scaleY(-1);"
>
<!-- 系统消息 -->
<div v-if="item.msg_type >= 1000" class="message-box">
<component :is="MessageComponents[item.msg_type] || 'unknown-message'" :extra="item.extra" :data="item" />
<component
:is="MessageComponents[item.msg_type] || 'unknown-message'"
:extra="item.extra"
:data="item"
/>
</div>
<!-- 撤回消息 -->
<div v-else-if="item.is_revoke == 1" class="message-box">
<revoke-message :login_uid="userStore.uid" :user_id="item.user_id" :nickname="item.nickname"
:talk_type="item.talk_type" :datetime="item.created_at" />
<revoke-message
:login_uid="userStore.uid"
:user_id="item.user_id"
:nickname="item.nickname"
:talk_type="item.talk_type"
:datetime="item.created_at"
/>
</div>
<div v-else class="message-box record-box" :class="{
<div
v-else
class="message-box record-box"
:class="{
'direction-rt': item.float == 'right',
'multi-select': dialogueStore.isOpenMultiSelect,
'multi-select-check': item.isCheck
}">
'multi-select-check': item.isCheck,
}"
>
<!-- 多选按钮 -->
<aside v-if="dialogueStore.isOpenMultiSelect" class="checkbox-column">
<aside
v-if="dialogueStore.isOpenMultiSelect"
class="checkbox-column"
>
<!-- <n-checkbox size="small" :checked="item.isCheck" @update:checked="item.isCheck = !item.isCheck" /> -->
<tm-checkbox :round="10" :defaultChecked="item.isCheck"
@update:modelValue="item.isCheck = !item.isCheck" :size="42" color="#46299D"></tm-checkbox>
<tm-checkbox
:round="10"
:defaultChecked="item.isCheck"
@update:modelValue="item.isCheck = !item.isCheck"
:size="42"
color="#46299D"
></tm-checkbox>
</aside>
<!-- 头像信息 -->
<aside class="avatar-column" @click="toUserDetailPage(item)">
<im-avatar class="pointer" :src="item.avatar" :size="80" :username="item.nickname"
@click="showUserInfoModal(item.user_id)" />
<im-avatar
class="pointer"
:src="item.avatar"
:size="80"
:username="item.nickname"
@click="showUserInfoModal(item.user_id)"
/>
</aside>
<!-- 主体信息 -->
<main class="main-column">
<div class="talk-title">
<span class="nickname pointer" v-show="talkParams.type == 2 && item.float == 'left'"
@click="onClickNickname(item)">
<span class="at">@</span>{{ item.nickname }}
<span
class="nickname pointer"
v-show="talkParams.type == 2 && item.float == 'left'"
@click="onClickNickname(item)"
>
<span class="at">@</span>
{{ item.nickname }}
</span>
<span>
{{ parseTime(item.created_at, '{m}/{d} {h}:{i}') }}
</span>
<span>{{ parseTime(item.created_at, '{m}/{d} {h}:{i}') }}</span>
</div>
<div class="talk-content" :class="{ pointer: dialogueStore.isOpenMultiSelect }">
<div
class="talk-content"
:class="{ pointer: dialogueStore.isOpenMultiSelect }"
>
<deepBubble
@clickMenu="(menuType) => onContextMenu(menuType, item)"
:isShowCopy="isShowCopy(item)"
:isShowWithdraw="isRevoke(talkParams.uid,item)"
:isShowWithdraw="isRevoke(talkParams.uid, item)"
>
<component class="component-content" :key="item.zp_index"
:is="MessageComponents[item.msg_type] || 'unknown-message'" :extra="item.extra" :data="item"
:max-width="true" :source="'panel'" />
<component
class="component-content"
:key="item.zp_index"
:is="MessageComponents[item.msg_type] || 'unknown-message'"
:extra="item.extra"
:data="item"
:max-width="true"
:source="'panel'"
/>
</deepBubble>
<!-- <div class="talk-tools">
<template v-if="talkParams.type == 1 && item.float == 'right'">
@ -91,8 +154,11 @@
</div> -->
</div>
<div v-if="item.extra.reply" class="talk-reply pointer"
@click="onJumpMessage(item.extra?.reply?.msg_id)">
<div
v-if="item.extra.reply"
class="talk-reply pointer"
@click="onJumpMessage(item.extra?.reply?.msg_id)"
>
<!-- <n-icon :component="ToTop" size="14" class="icon-top" /> -->
<span class="ellipsis">
回复 {{ item.extra?.reply?.nickname }}:
@ -101,60 +167,104 @@
</div>
</main>
</div>
</div>
<div class="load-toolbar pointer" style="transform: scaleY(-1);">
<span v-if="loadConfig.status == 0"> 正在加载数据中 ... </span>
<span v-else-if="loadConfig.status == 1" @click="onRefreshLoad"> 查看更多消息 ... </span>
<span v-else class="no-more"> 没有更多消息了 </span>
</div>
</ZPaging>
<span v-if="loadConfig.status == 0">正在加载数据中 ...</span>
<span v-else-if="loadConfig.status == 1" @click="onRefreshLoad">
查看更多消息 ...
</span>
<span v-else class="no-more">没有更多消息了</span>
</div>
</div>
<template #bottom>
<div class="footBox">
<div v-if="!dialogueStore.isOpenMultiSelect">
<div class="mt-[16rpx] ml-[32rpx] mr-[32rpx] flex items-center justify-between">
<div
class="pt-[16rpx] ml-[32rpx] mr-[32rpx] flex items-center justify-between"
>
<div class="flex-1 quillBox">
<QuillEditor ref="editor" id="editor" :options="editorOption" @editorChange="onEditorChange"
style="height: 100%; border: none" />
<QuillEditor
ref="editor"
id="editor"
:options="editorOption"
@editorChange="onEditorChange"
style="height: 100%; border: none;"
/>
<!-- <tm-input type=textarea autoHeight focusColor="#F9F9F9" color="#F9F9F9" :inputPadding="[12]"
placeholder=""></tm-input> -->
</div>
<tm-image :margin="[10, 0]" @click="handleEmojiPanel" :width="52" :height="52" :round="12"
:src="state.isOpenEmojiPanel ? keyboard : smile"></tm-image>
<tm-image @click="handleFilePanel" :margin="[10, 0]" :width="52" :height="52" :round="12"
:src="addCircleGray"></tm-image>
<tm-button @click="onSendMessageClick" :margin="[0, 0]" :padding="[0, 30]" color="#46299D" :fontSize="28"
size="mini" :shadow="0" label="发送"></tm-button>
<tm-image
:margin="[10, 0]"
@click="handleEmojiPanel"
:width="52"
:height="52"
:round="12"
:src="state.isOpenEmojiPanel ? keyboard : smile"
></tm-image>
<tm-image
@click="handleFilePanel"
:margin="[10, 0]"
:width="52"
:height="52"
:round="12"
:src="addCircleGray"
></tm-image>
<tm-button
@click="onSendMessageClick"
:margin="[0, 0]"
:padding="[0, 30]"
color="#46299D"
:fontSize="28"
size="mini"
:shadow="0"
label="发送"
></tm-button>
</div>
<div v-if="state.isOpenEmojiPanel" class="mt-[50rpx]">
<emojiPanel @on-select="onEmoticonEvent" />
</div>
<div v-if="state.isOpenFilePanel" class="mt-[16rpx]">
<filePanel @selectImg="handleSelectImg" :talkParams="talkParams" />
<filePanel
@selectImg="handleSelectImg"
:talkParams="talkParams"
/>
</div>
</div>
<div v-else class="h-[232rpx]">
<div class="flex items-center justify-center mt-[12rpx] text-[24rpx] text-[#747474] leading-[44rpx]">
<div
class="flex items-center justify-center mt-[12rpx] text-[24rpx] text-[#747474] leading-[44rpx]"
>
<div class="mr-[8rpx]">已选中:</div>
<div>{{ selectedMessage.length }}条消息</div>
</div>
<div class="flex items-center justify-around pl-[128rpx] pr-[128rpx] mt-[18rpx] text-[20rpx] text-[#737373] ">
<div @click="handleMergeForward" class="flex flex-col items-center justify-center">
<div
class="flex items-center justify-around pl-[128rpx] pr-[128rpx] mt-[18rpx] text-[20rpx] text-[#737373]"
>
<div
@click="handleMergeForward"
class="flex flex-col items-center justify-center"
>
<tm-image :width="68" :height="68" :src="zu6050"></tm-image>
<div class="mt-[6rpx]">合并转发</div>
</div>
<div @click="handleSingleForward" class="flex flex-col items-center justify-center">
<div
@click="handleSingleForward"
class="flex flex-col items-center justify-center"
>
<tm-image :width="68" :height="68" :src="zu6051"></tm-image>
<div class="mt-[6rpx]">逐条转发</div>
</div>
<div @click="handleWechatForward" class="flex flex-col items-center justify-center">
<div
@click="handleWechatForward"
class="flex flex-col items-center justify-center"
>
<tm-image :width="68" :height="68" :src="zu6052"></tm-image>
<div class="mt-[6rpx]">微信</div>
</div>
<div @click="handleDelete" class="flex flex-col items-center justify-center">
<div
@click="handleDelete"
class="flex flex-col items-center justify-center"
>
<tm-image :width="68" :height="68" :src="zu6053"></tm-image>
<div class="mt-[6rpx]">删除</div>
</div>
@ -170,29 +280,47 @@
:height="416"
:round="6"
>
<div class="w-full h-full flex flex-col items-center" >
<div class="mt-[46rpx] mb-[44rpx] leading-[48rpx] text-[#747474] text-[24rpx]" >
<div class="w-full h-full flex flex-col items-center">
<div
class="mt-[46rpx] mb-[44rpx] leading-[48rpx] text-[#747474] text-[24rpx]"
>
撤回该条消息
</div>
<div class="divider" ></div>
<div @click="withdrawerConfirm" class="mt-[32rpx] mb-[32rpx] text-[32rpx] text-[#CF3050] leading-[48rpx]">
<div class="divider"></div>
<div
@click="withdrawerConfirm"
class="mt-[32rpx] mb-[32rpx] text-[32rpx] text-[#CF3050] leading-[48rpx]"
>
撤回
</div>
<div class="divider" ></div>
<div @click="state.showWin = false" class="mt-[32rpx] mb-[32rpx] text-[32rpx] text-[#000000] leading-[48rpx]">
<div class="divider"></div>
<div
@click="state.showWin = false"
class="mt-[32rpx] mb-[32rpx] text-[32rpx] text-[#000000] leading-[48rpx]"
>
取消
</div>
</div>
</tm-drawer>
</div>
</template>
</ZPaging>
</div>
</template>
<script setup>
import { ref, reactive, watch, computed, onMounted, onUnmounted, nextTick } from 'vue';
import {
ref,
reactive,
watch,
computed,
onMounted,
onUnmounted,
nextTick,
} from 'vue'
import { QuillEditor, Quill } from '@vueup/vue-quill'
import EmojiBlot from './formats/emoji'
import { useChatList } from "@/store/chatList/index.js";
import { useAuth } from "@/store/auth";
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import {
useUserStore,
useDialogueStore,
@ -200,41 +328,45 @@ import {
useEditorDraftStore,
useTalkStore,
useSettingsStore,
useDialogueListStore
useDialogueListStore,
} from '@/store'
import addCircleGray from "@/static/image/chatList/addCircleGray.png";
import addCircleGray from '@/static/image/chatList/addCircleGray.png'
import { MessageComponents, ForwardableMessageType } from '@/constant/message'
import { formatTime, parseTime } from '@/utils/datetime'
import { deltaToMessage, deltaToString, isEmptyDelta } from './util'
import smile from "@/static/image/chatList/smile@2x.png";
import keyboard from "@/static/image/chatList/keyboard@2x.png";
import smile from '@/static/image/chatList/smile@2x.png'
import keyboard from '@/static/image/chatList/keyboard@2x.png'
import { useInject, useTalkRecord } from '@/hooks'
import { emitCall } from '@/utils/common'
import ZPaging from "@/uni_modules/z-paging/components/z-paging/z-paging.vue";
import useZPaging from "@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js";
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'
import emojiPanel from './components/emojiPanel.vue'
import filePanel from './components/filePanel.vue'
import lodash from "lodash";
import lodash from 'lodash'
import { ServePublishMessage } from '@/api/chat'
import copy07 from "@/static/image/chatList/copy07@2x.png"
import multipleChoices from "@/static/image/chatList/multipleChoices@2x.png"
import cite from "@/static/image/chatList/cite@2x.png"
import withdraw from "@/static/image/chatList/withdraw@2x.png"
import delete07 from "@/static/image/chatList/delete@2x.png"
import zu6050 from "@/static/image/chatList/zu6050@2x.png"
import zu6051 from "@/static/image/chatList/zu6051@2x.png"
import zu6052 from "@/static/image/chatList/zu6052@2x.png"
import zu6053 from "@/static/image/chatList/zu6053@2x.png"
import deepBubble from "@/components/deep-bubble/deep-bubble.vue"
import {isRevoke } from './menu'
import copy07 from '@/static/image/chatList/copy07@2x.png'
import multipleChoices from '@/static/image/chatList/multipleChoices@2x.png'
import cite from '@/static/image/chatList/cite@2x.png'
import withdraw from '@/static/image/chatList/withdraw@2x.png'
import delete07 from '@/static/image/chatList/delete@2x.png'
import zu6050 from '@/static/image/chatList/zu6050@2x.png'
import zu6051 from '@/static/image/chatList/zu6051@2x.png'
import zu6052 from '@/static/image/chatList/zu6052@2x.png'
import zu6053 from '@/static/image/chatList/zu6053@2x.png'
import deepBubble from '@/components/deep-bubble/deep-bubble.vue'
import { isRevoke } from './menu'
import useConfirm from '@/components/x-confirm/useConfirm.js'
import { onLoad as uniOnload } from '@dcloudio/uni-app'
Quill.register('formats/emoji', EmojiBlot)
const { getDialogueList, updateZpagingRef, virtualList } = useDialogueListStore()
const {
getDialogueList,
updateZpagingRef,
virtualList,
} = useDialogueListStore()
const talkStore = useTalkStore()
const { showConfirm } = useConfirm();
const { showConfirm } = useConfirm()
const settingsStore = useSettingsStore()
const userStore = useUserStore()
const dialogueStore = useDialogueStore()
@ -251,7 +383,7 @@ const talkParams = reactive({
username: computed(() => dialogueStore.talk.username),
online: computed(() => dialogueStore.online),
keyboard: computed(() => dialogueStore.keyboard),
num: computed(() => dialogueStore.members.length)
num: computed(() => dialogueStore.members.length),
})
const state = ref({
@ -259,7 +391,7 @@ const state = ref({
isOpenFilePanel: false,
showWin: false,
onfocusItem: null,
sessionId: ''
sessionId: '',
})
uniOnload((options) => {
@ -269,11 +401,11 @@ uniOnload((options) => {
})
const handleEmojiPanel = () => {
state.value.isOpenFilePanel = false;
state.value.isOpenFilePanel = false
state.value.isOpenEmojiPanel = !state.value.isOpenEmojiPanel
}
const handleFilePanel = () => {
state.value.isOpenEmojiPanel = false;
state.value.isOpenEmojiPanel = false
state.value.isOpenFilePanel = !state.value.isOpenFilePanel
}
@ -282,8 +414,8 @@ const onSendMessage = (data = {}) => {
...data,
receiver: {
receiver_id: talkParams.receiver_id,
talk_type: talkParams.type
}
talk_type: talkParams.type,
},
}
ServePublishMessage(message)
@ -313,10 +445,11 @@ const onSendMessageClick = () => {
return message.info('发送内容超长,请分条发送')
}
onSendTextEvent({
data, callBack: (ok) => {
data,
callBack: (ok) => {
if (!ok) return
getQuill().setContents([], Quill.sources.USER)
}
},
})
break
}
@ -330,7 +463,7 @@ const onSendTextEvent = lodash.throttle((value) => {
type: 'text',
content: data.items[0].content,
quote_id: data.quoteId,
mentions: data.mentionUids
mentions: data.mentionUids,
}
onSendMessage(message)
@ -340,7 +473,7 @@ const onSendTextEvent = lodash.throttle((value) => {
const onInputEvent = ({ data }) => {
talkStore.updateItem({
index_name: indexName.value,
draft_text: data
draft_text: data,
})
// 线
@ -365,7 +498,13 @@ const evnets = {
},
}
const { loadConfig, records, onLoad, onRefreshLoad, onJumpMessage } = useTalkRecord(talkParams.uid)
const {
loadConfig,
records,
onLoad,
onRefreshLoad,
onJumpMessage,
} = useTalkRecord(talkParams.uid)
const getQuill = () => {
return editor.value?.getQuill()
@ -384,11 +523,10 @@ const isShowCopy = (item) => {
default:
return false
}
}
const selectedMessage = computed(() => {
return virtualList.value.filter(item => item.isCheck)
return virtualList.value.filter((item) => item.isCheck)
})
//
@ -421,7 +559,7 @@ const onEmoticonEvent = (data) => {
quill.setSelection(index + 1, 0, 'user')
} else {
let fn = emitCall('emoticon_event', data.value, () => { })
let fn = emitCall('emoticon_event', data.value, () => {})
emit('editor-event', fn)
}
}
@ -434,7 +572,7 @@ const onEditorChange = () => {
if (!isEmptyDelta(delta)) {
editorDraftStore.items[indexName.value || ''] = JSON.stringify({
text: text,
ops: delta.ops
ops: delta.ops,
})
} else {
// editorDraftStore.items
@ -452,7 +590,7 @@ const onClipboardMatcher = (node, Delta) => {
if (op.insert && typeof op.insert === 'string') {
ops.push({
insert: op.insert, //
attributes: {} //
attributes: {}, //
})
} else {
ops.push(op)
@ -469,16 +607,16 @@ const editorOption = {
toolbar: false,
clipboard: {
//
matchers: [[Node.ELEMENT_NODE, onClipboardMatcher]]
matchers: [[Node.ELEMENT_NODE, onClipboardMatcher]],
},
keyboard: {
bindings: {
enter: {
key: 13,
handler: onSendMessageClick
}
}
handler: onSendMessageClick,
},
},
},
// imageUploader: {
@ -527,49 +665,49 @@ const virtualListChange = (vList) => {
}
const onContextMenu = (menuType, item) => {
console.log(menuType, item, 'item');
console.log(menuType, item, 'item')
switch (menuType) {
case 'actionCopy':
actionCopy(item)
break;
break
case 'multipleChoose':
multipleChoose(item)
break;
break
case 'actionCite':
actionCite(item)
break;
break
case 'actionWithdraw':
actionWithdraw(item)
break;
break
case 'actionDelete':
actionDelete(item)
break;
break
default:
break;
break
}
}
const actionCopy = (item) => {
console.log('复制');
console.log('复制')
let content = ''
switch (item.msg_type) {
case 1:
content = item.extra.content
break;
break
case 3:
content = item.extra.url
break;
break
case 5:
content = item.extra.url
break;
break
default:
break;
break
}
uni.setClipboardData({
data: content,
});
})
}
const multipleChoose = (item) => {
@ -578,23 +716,23 @@ const multipleChoose = (item) => {
}
const actionCite = (item) => {
console.log('引用');
console.log('引用')
}
const actionWithdraw = (item) => {
console.log('撤回');
console.log('撤回')
state.value.onfocusItem = item
state.value.showWin = true;
state.value.showWin = true
}
const withdrawerConfirm = () => {
dialogueStore.ApiRevokeRecord(state.value.onfocusItem.msg_id)
state.value.onfocusItem = null
state.value.showWin = false;
state.value.showWin = false
}
const actionDelete = (item) => {
console.log('删除');
console.log('删除')
item.isCheck = true
handleDelete()
}
@ -603,14 +741,14 @@ const handleMergeForward = () => {
if (selectedMessage.value.length == 0) {
return message.warning('未选择消息')
}
console.log('合并转发');
console.log('合并转发')
dialogueStore.setForwardType(2)
dialogueStore.setForwardMessages(selectedMessage.value)
uni.navigateTo({
url: '/pages/chooseChat/index',
success: function (res) {
clearMultiSelect()
}
},
})
}
@ -618,14 +756,14 @@ const handleSingleForward = () => {
if (selectedMessage.value.length == 0) {
return message.warning('未选择消息')
}
console.log('逐条转发');
console.log('逐条转发')
dialogueStore.setForwardType(1)
dialogueStore.setForwardMessages(selectedMessage.value)
uni.navigateTo({
url: '/pages/chooseChat/index',
success: function (res) {
clearMultiSelect()
}
},
})
}
@ -633,40 +771,42 @@ const handleWechatForward = () => {
if (selectedMessage.value.length == 0) {
return message.warning('未选择消息')
}
console.log('微信转发');
console.log('微信转发')
}
const handleDelete = () => {
if (selectedMessage.value.length == 0) {
return message.warning('未选择消息')
}
console.log('删除');
console.log('删除')
showConfirm({
content: '确定删除聊天记录',
confirmText:'删除',
confirmColor:'#CF3050',
confirmText: '删除',
confirmColor: '#CF3050',
onConfirm: async () => {
const msgIds = selectedMessage.value.map(item => item.msg_id)
virtualList.value = virtualList.value.filter(item => !msgIds.includes(item.msg_id))
const msgIds = selectedMessage.value.map((item) => item.msg_id)
virtualList.value = virtualList.value.filter(
(item) => !msgIds.includes(item.msg_id),
)
dialogueStore.ApiDeleteRecord(msgIds)
clearMultiSelect()
},
onCancel: () => {
}
onCancel: () => {},
})
}
watch(() => zpagingRef.value, (newValue, oldValue) => {
watch(
() => zpagingRef.value,
(newValue, oldValue) => {
if (newValue) {
updateZpagingRef(newValue)
}
})
},
)
const clearMultiSelect = () => {
dialogueStore.setMultiSelect(false)
virtualList.value.forEach(item => {
virtualList.value.forEach((item) => {
item.isCheck = false
})
}
@ -689,14 +829,19 @@ const initData = async () => {
//
const toChatSettingsPage = () => {
uni.navigateTo({
url: '/pages/chatSettings/index?groupId=' + talkParams?.receiver_id + '&sessionId=' + state.sessionId
url:
'/pages/chatSettings/index?groupId=' +
talkParams?.receiver_id +
'&sessionId=' +
state.sessionId,
})
}
//
const toUserDetailPage = (userItem) => {
uni.navigateTo({
url: '/pages/dialog/dialogDetail/userDetail?erpUserId=' + userItem.erp_user_id,
url:
'/pages/dialog/dialogDetail/userDetail?erpUserId=' + userItem.erp_user_id,
})
}
@ -710,24 +855,23 @@ onUnmounted(() => {
})
</script>
<style scoped lang="less">
uni-page-body,
page {
height: 100%;
}
.outer-layer {
.dialog-page {
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
display: flex;
flex-direction: column;
overflow: hidden;
}
background-position: bottom center;
background-attachment: fixed;
width: 100%;
.root {
flex: 1;
.dialog-list {
padding: 20rpx 32rpx;
min-height: 0;
}
.toChatSetting_btn {
::v-deep .tmicon-gengduo {
line-height: unset !important;
}
}
}
.searchRoot {
@ -745,22 +889,6 @@ page {
background-color: #fff;
}
.dialogBox {
height: 100%;
min-height: 0;
overflow: auto;
//
&::-webkit-scrollbar {
display: none;
}
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
}
.load-toolbar {
height: 50rpx;
color: #409eff;
@ -825,7 +953,7 @@ page {
margin-bottom: 6rpx;
font-size: 24rpx;
user-select: none;
color: #BABABA;
color: #bababa;
opacity: 1;
&.show {
@ -970,7 +1098,7 @@ page {
:deep(.ql-editor) {
padding: 14rpx 22rpx;
background-color: #F9F9F9;
background-color: #f9f9f9;
border-radius: 8rpx;
outline: none !important;
max-height: 294rpx;
@ -1011,6 +1139,6 @@ page {
.divider {
width: 100%;
height: 1rpx;
background-color: #E7E7E7;
background-color: #e7e7e7;
}
</style>

View File

@ -11,12 +11,22 @@
:maxCount="99"
color="#D03050"
>
<tm-image
:width="96"
:height="96"
:round="12"
:src="avatarCpt"
></tm-image>
<avatarModule
:mode="props?.data?.group_type === 0 ? 1 : 2"
:avatar="props?.data?.avatar"
:groupType="props?.data?.group_type"
:userName="props?.data?.name"
:customStyle="{
width: '96rpx',
height: '96rpx',
}"
:customTextStyle="{
fontSize: '32rpx',
fontWeight: 'bold',
color: '#fff',
lineHeight: '44rpx',
}"
></avatarModule>
</tm-badge>
</div>
<div class="chatInfo">
@ -54,9 +64,10 @@
@click="handleTop"
class="w-[156rpx] h-[154rpx] text-[#ffffff] bg-[#F09F1F] flex items-center justify-center"
>
{{ props.data.is_top === 1 ? "取消置顶" : "置顶" }}
{{ props.data.is_top === 1 ? '取消置顶' : '置顶' }}
</div>
<div
@click="handleDelete"
class="w-[156rpx] h-[154rpx] text-[#ffffff] bg-[#CF3050] flex items-center justify-center"
>
删除
@ -71,20 +82,17 @@
</div>
</template>
<script setup>
import { ref, reactive, defineProps,computed } from "vue";
import dayjs from "dayjs";
import { beautifyTime } from "@/utils/datetime";
import { ServeClearTalkUnreadNum } from "@/api/chat";
import { useTalkStore, useDialogueStore } from "@/store";
import { useSessionMenu } from "@/hooks";
import zu4989 from "@/static/image/chatList/zu4989@2x.png";
import zu4991 from "@/static/image/chatList/zu4991@2x.png";
import zu4992 from "@/static/image/chatList/zu4992@2x.png";
import zu5296 from "@/static/image/chatList/zu5296@2x.png";
import avatarModule from '@/components/avatar-module/index.vue'
import { ref, reactive, defineProps, computed } from 'vue'
import dayjs from 'dayjs'
import { beautifyTime } from '@/utils/datetime'
import { ServeClearTalkUnreadNum } from '@/api/chat'
import { useTalkStore, useDialogueStore } from '@/store'
import { useSessionMenu } from '@/hooks'
const talkStore = useTalkStore();
const { onToTopTalk } = useSessionMenu();
const dialogueStore = useDialogueStore();
const talkStore = useTalkStore()
const { onToTopTalk, onRemoveTalk } = useSessionMenu()
const dialogueStore = useDialogueStore()
const props = defineProps({
data: {
type: Object,
@ -96,35 +104,12 @@ const props = defineProps({
default: -1,
required: true,
},
});
const avatarCpt = computed(() => {
let avatar = null;
if (props.data.avatar !== "") {
avatar = props.data.avatar;
} else {
switch (props.data.group_type) {
case 1:
avatar = zu4992;
break;
case 2:
avatar = zu4989;
break;
case 3:
avatar = zu4991;
break;
case 4:
avatar = zu5296;
break;
}
}
return avatar;
});
})
const cellClick = () => {
console.log(props.data);
console.log(props.data)
//
dialogueStore.setDialogue(props.data);
dialogueStore.setDialogue(props.data)
//
if (props.data.unread_num > 0) {
@ -135,18 +120,24 @@ const cellClick = () => {
talkStore.updateItem({
index_name: props.data.index_name,
unread_num: 0,
});
});
})
})
}
uni.navigateTo({
url: '/pages/dialog/index?sessionId=' + props.data.id,
});
};
})
}
const handleTop = () => {
console.log(props.data, 1);
onToTopTalk(props.data);
};
console.log(props.data, 1)
onToTopTalk(props.data)
}
//
const handleDelete = () => {
console.log(props.data)
onRemoveTalk(props.data)
}
</script>
<style lang="scss" scoped>
.chatItem {
@ -161,11 +152,6 @@ const handleTop = () => {
}
}
.avatarImg {
height: 96rpx;
width: 96rpx;
}
.chatInfo {
flex: 1;
margin-left: 20rpx;

View File

@ -25,8 +25,13 @@
:src="addCircle"
></tm-image>
<template v-slot:label>
<div class="w-full h-[208rpx] pt-[22rpx] pb-[22rpx] pl-[34rpx] pr-[32rpx]" >
<div @click="creatGroupChat" class="flex items-center mb-[30rpx]" >
<div
class="w-full h-[208rpx] pt-[22rpx] pb-[22rpx] pl-[34rpx] pr-[32rpx]"
>
<div
@click="creatGroupChat"
class="flex items-center mb-[30rpx]"
>
<div class="mr-[26rpx] flex items-center">
<tm-image
:width="40"
@ -34,10 +39,17 @@
:src="cahtPopover"
></tm-image>
</div>
<div class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold" >发起群聊</div>
<div
class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold"
>
发起群聊
</div>
<div class="divider" ></div>
<div class="flex items-center mt-[28rpx]" >
</div>
<div class="divider"></div>
<div
@click="toAddressBookPage"
class="flex items-center mt-[28rpx]"
>
<div class="mr-[26rpx] flex items-center">
<tm-image
:width="40"
@ -45,7 +57,9 @@
:src="zu3289"
></tm-image>
</div>
<div class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold" >
<div
class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold"
>
通讯录
</div>
</div>
@ -78,24 +92,25 @@
</div>
</template>
<script setup>
import { ref, watch, computed } from "vue";
import { onShow, onLoad } from "@dcloudio/uni-app";
import { useChatList } from "@/store/chatList/index.js";
import { useAuth } from "@/store/auth";
import { useTalkStore, useUserStore } from "@/store";
import chatItem from "./components/chatItem.vue";
import addCircle from "@/static/image/chatList/addCircle.png";
import cahtPopover from "@/static/image/chatList/cahtPopover.png";
import zu3289 from "@/static/image/chatList/zu3289@2x.png";
import { ref, watch, computed } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import { useTalkStore, useUserStore, useDialogueStore } from '@/store'
import chatItem from './components/chatItem.vue'
import addCircle from '@/static/image/chatList/addCircle.png'
import cahtPopover from '@/static/image/chatList/cahtPopover.png'
import zu3289 from '@/static/image/chatList/zu3289@2x.png'
const talkStore = useTalkStore();
const userStore = useUserStore();
const { userInfo } = useAuth();
const talkStore = useTalkStore()
const userStore = useUserStore()
const dialogueStore = useDialogueStore()
const { userInfo } = useAuth()
const topItems = computed(() => talkStore.topItems);
const topItems = computed(() => talkStore.topItems)
const items = computed(() => {
// if (searchKeyword.value.length === 0) {
return talkStore.talkItems;
return talkStore.talkItems
// }
// return talkStore.talkItems.filter((item) => {
@ -103,31 +118,64 @@ const items = computed(() => {
// return keyword.toLowerCase().indexOf(searchKeyword.value.toLowerCase()) != -1
// })
});
})
const creatGroupChat = () => {
uni.navigateTo({
url: "/pages/creatGroupChat/index",
});
};
url: '/pages/creatGroupChat/index',
})
}
const toSearchPage = () => {
uni.navigateTo({
url: "/pages/search/index",
});
};
url: '/pages/search/index',
})
}
//
const toAddressBookPage = () => {
uni.navigateTo({
url: '/pages/chooseByDeps/index?chooseMode=3',
})
}
watch(
() => talkStore,
(newValue, oldValue) => {
console.log(talkStore);
// console.log(talkStore)
},
{ deep: true, immediate: true }
);
{ deep: true, immediate: true },
)
onShow(() => {
talkStore.loadTalkList();
});
talkStore.loadTalkList()
})
onLoad((options) => {
if (options?.openSessionIndexName) {
if (items?.value?.length > 0) {
items.value.forEach((openSession) => {
if (openSession.index_name === options?.openSessionIndexName) {
dialogueStore.setDialogue(openSession)
if (openSession.unread_num > 0) {
ServeClearTalkUnreadNum({
talk_type: openSession.talk_type,
receiver_id: openSession.receiver_id,
}).then(() => {
talkStore.updateItem({
index_name: openSession.index_name,
unread_num: 0,
})
})
}
uni.navigateTo({
url: '/pages/dialog/index?sessionId=' + openSession.id,
})
}
})
}
}
})
</script>
<style scoped lang="scss">
uni-page-body,
@ -137,7 +185,7 @@ page {
.outer-layer {
overflow-y: auto;
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
padding: 0 32rpx 20rpx 32rpx;
display: flex;
@ -155,14 +203,14 @@ page {
margin-top: 20rpx;
background-color: #fff;
}
.divider{
.divider {
height: 1rpx;
background-color: #7C7C7C;
background-color: #7c7c7c;
opacity: 0.6;
}
.popoverBox {
:deep(.popover-bcc){
transform:translateX(20rpx) translateY(40rpx);
:deep(.popover-bcc) {
transform: translateX(20rpx) translateY(40rpx);
}
}
</style>

View File

@ -9,15 +9,23 @@
: ''
"
>
<div
class="avatar-img"
:class="props?.conditionType ? 'avatar-img-condition' : ''"
>
<img v-if="avatarImg !== 'textImg'" :src="avatarImg" />
<span v-if="avatarImg === 'textImg'" class="text-[32rpx] font-bold">
{{ imgText }}
</span>
</div>
<avatarModule
:mode="props.searchItem?.group_type === 0 ? 1 : 2"
:avatar="avatarImg"
:userName="resultName"
:groupType="props.searchItem?.group_type"
:customStyle="{
width: props?.conditionType ? '64rpx' : '96rpx',
height: props?.conditionType ? '64rpx' : '96rpx',
margin: props?.conditionType ? '0 18rpx 0 0' : '0 20rpx 0 0',
}"
:customTextStyle="{
fontSize: props?.conditionType ? '20rpx' : '32rpx',
fontWeight: 'bold',
color: '#fff',
lineHeight: '44rpx',
}"
></avatarModule>
<div class="result-info">
<div
class="info-name"
@ -75,10 +83,7 @@
</div>
</template>
<script setup>
import zu4992 from '@/static/image/chatList/zu4992@2x.png'
import zu4991 from '@/static/image/chatList/zu4991@2x.png'
import zu4989 from '@/static/image/chatList/zu4989@2x.png'
import zu5296 from '@/static/image/chatList/zu5296@2x.png'
import avatarModule from '@/components/avatar-module/index.vue'
import {
ref,
watch,
@ -167,14 +172,8 @@ const getKeyValue = (keys) => {
//
const avatarImg = computed(() => {
let avatar = getKeyValue(keyMapping[props.searchResultKey]?.avatar)
if (!avatar) {
avatar = groupTypeMapping[props.searchItem?.group_type]?.defaultImg
}
if (props?.conditionType) {
avatar = props.searchItem.avatar
if (!avatar) {
avatar = groupTypeMapping[0]?.defaultImg
}
}
return avatar
})
@ -195,25 +194,20 @@ const imgText = computed(() => {
// -groupType
const groupTypeMapping = {
0: {
defaultImg: 'textImg',
},
1: {
defaultImg: zu4992,
},
2: {
result_type: t('index.mine.department'),
result_type_color: '#377EC6',
defaultImg: zu4989,
},
3: {
result_type: t('index.mine.project'),
result_type_color: '#C1681C',
defaultImg: zu4991,
},
4: {
result_type: t('index.type.company'),
result_type_color: '#7A58DE',
defaultImg: zu5296,
},
}
//
@ -262,35 +256,6 @@ const resultDetail = computed(() => {
padding: 22rpx 0 24rpx;
border-bottom: 1px solid $theme-border-color;
.avatar-img {
width: 96rpx;
height: 96rpx;
margin: 0 20rpx 0 0;
border-radius: 50%;
overflow: hidden;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background: linear-gradient(to right, #674bbc, #46299d);
flex-shrink: 0;
img {
width: 100%;
height: 100%;
}
span {
color: #fff;
line-height: 44rpx;
}
}
.avatar-img-condition {
width: 64rpx;
height: 64rpx;
margin: 0 18rpx 0 0;
span {
font-size: 20rpx;
}
}
.result-info {
width: 100%;
.info-name {

View File

@ -11,13 +11,7 @@
:inside-more="true"
>
<template #top v-if="state.showPageTitle">
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
<div class="navBar-title flex flex-col items-center justify-center">
<span class="text-[34rpx] font-medium">
{{ state.pageTitle }}
</span>
</div>
</tm-navbar>
<customNavbar :title="state.pageTitle"></customNavbar>
</template>
<div v-if="state.condition === 'date'" class="search-by-date">
<tm-time-picker

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -5,7 +5,7 @@ import { userInfoApi } from "@/api/user";
import {ref} from 'vue'
export const useAuth = createGlobalState(() => {
// const token = useStorage('token', '', uniStorage)
const token = ref('79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941ca1430937103230a1e32a1715f569f3efdbe6f8cb8b7b8642bd679668081b9b08f693d1b5be6002d936ec51e1e3e0c4927de9e32ac99a109b326e5d2bda27ec87624bb416ec70d2a95a2e190feeba9f0d6bae8571b3dfe89c824712344759a8f2bff9d70747c52525cf6a5614f9c770bca461a9b9c247b6dca97bcf83bbaf99bb726752c4fe1e9a4aa7de5c4cf3e88a3e480801280d45cdc124f9d8221105d8529c7c268c1251c3477bff5c051043bf9803f725aa97cbe369f1902315052eb4ee0e925840addd5e7796254066f2f9eb3f99f69f3d0db26bd940f47a18fa7b6182c6c7de87ee1a00936b7c90024c6085c65ffc3b8d20562eea48ff0b303a7795c67')
const token = ref('79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941caaef1334d640773710f8cd96473bacfb190cba595a5d6a9c87d70f0999a3ebb41147213b31b4bdccffca66a56acf3baab5af0154f0dce360079f37709f78e13711036899344bddb0fb4cf0f2890287cb62c3fcbe33368caa5e213624577be8b8420ab75b1f50775ee16142a4321c5d56995f37354a66a969da98d95ba6e65d142ed097e04b411c1ebad2f62866d0ec7e1838420530a9941dbbcd00490199f8b892d39c9c0de979228015326a8fde48c55b8d48be643cb00da04330faa68977c689c6d523213435a2d92e3fab2976a1f5a6d4bfcb6c7aba3b4924873003b6492a2d0b81da4722162c68aeeec942eb2a85d')
const refreshToken = useStorage('refreshToken', '', uniStorage)
const userInfo = useStorage('userInfo', {}, uniStorage)
const leaderList = useStorage('leaderList', [], uniStorage)

View File

@ -63,6 +63,8 @@ export const useGroupTypeStore = createGlobalState(() => {
crumbsIndex.value = 0
depCheckedKeys.value = []
groupAdmins.value = []
membersCheckedKeys.value = []
allChooseMembers.value = []
}
const createDepGroup = async (param) => {
@ -76,8 +78,10 @@ export const useGroupTypeStore = createGlobalState(() => {
}),
positionInfos: groupAdmins.value.map((v) => {
return {
position_id: v.ID,
position_name: v.name,
dept_id: v.dept_id,
dept_name: v.dept_name,
position_id: v.position_id,
position_name: v.position_name,
}
}),
})

View File

@ -4,10 +4,12 @@ import {
ServeRevokeRecords,
ServePublishMessage,
ServeCollectEmoticon,
ServeEmptyMessage,
} from '@/api/chat/index'
import { ServeGetGroupMembers } from '@/api/group/index'
import { useEditorStore } from './editor'
import { useDialogueListStore } from './dialogueList'
import { useAuth } from '../auth/index'
// 键盘消息事件定时器
// let keyboardTimeout = null
@ -73,7 +75,8 @@ export const useDialogueStore = defineStore('dialogue', {
getSilenceMember: (state) =>
state.members.filter((item) => item.is_mute === 1),
//获取群管理员
getAdminList: (state) => state.members.filter((item) => (item.leader === 1 || item.leader === 2)),
getAdminList: (state) =>
state.members.filter((item) => item.leader === 1 || item.leader === 2),
},
actions: {
// 更新在线状态
@ -121,6 +124,8 @@ export const useDialogueStore = defineStore('dialogue', {
key: o.key,
erp_user_id: o.erp_user_id,
is_mute: o.is_mute,
is_mine:
useAuth()?.userInfo?.value?.ID === o?.erp_user_id ? true : false,
}))
},
@ -210,6 +215,20 @@ export const useDialogueStore = defineStore('dialogue', {
})
},
//清空聊天记录
apiClearRecord() {
const { clearDialogueRecord } = useDialogueListStore()
ServeEmptyMessage({
talk_type: this.talk.talk_type,
receiver_id: this.talk.receiver_id,
}).then((res) => {
if (res.code == 200) {
clearDialogueRecord()
} else {
}
})
},
// 撤销聊天记录
ApiRevokeRecord(msg_id = '') {
ServeRevokeRecords({ msg_id }).then((res) => {

View File

@ -2,42 +2,44 @@ import { defineStore } from 'pinia'
import { useDialogueStore } from '@/store'
import lodash from 'lodash'
import {ref} from 'vue'
import {createGlobalState,useStorage} from '@vueuse/core'
import {uniStorage} from "@/utils/uniStorage.js"
import { ref } from 'vue'
import { createGlobalState, useStorage } from '@vueuse/core'
import { uniStorage } from '@/utils/uniStorage.js'
export const useDialogueListStore = createGlobalState(() => {
const dialogueList = useStorage('dialogueList', [], uniStorage)
const zpagingRef = ref()
const virtualList = ref([])
const getDialogueList=(indexName)=>{
return dialogueList.value.find(item=>item.index_name===indexName)
const getDialogueList = (indexName) => {
return dialogueList.value.find((item) => item.index_name === indexName)
}
const addDialogueRecord=(newRecords,type='add')=>{
console.log(newRecords);
const addDialogueRecord = (newRecords, type = 'add') => {
console.log(newRecords)
const dialogue = lodash.cloneDeep(useDialogueStore())
if (!dialogue || typeof dialogue !== 'object') return
// 检查是否已存在相同 index_name 的对话
const existingIndex = dialogueList.value.findIndex(item => item.index_name === dialogue.index_name)
const existingIndex = dialogueList.value.findIndex(
(item) => item.index_name === dialogue.index_name,
)
if (existingIndex === -1) {
// 如果不存在,直接添加
dialogueList.value.push(dialogue)
} else {
// 如果对话存在,处理 records 数组
const { records = [] } = dialogue
newRecords.forEach(newRecord => {
newRecords.forEach((newRecord) => {
const recordIndex = dialogueList.value[existingIndex].records.findIndex(
record => record.msg_id === newRecord.msg_id
(record) => record.msg_id === newRecord.msg_id,
)
if (recordIndex === -1) {
// 如果记录不存在,添加到 records 数组
if(type==='add'){
if (type === 'add') {
dialogueList.value[existingIndex].records.push(newRecord)
}else{
} else {
dialogueList.value[existingIndex].records.unshift(newRecord)
}
}
@ -47,66 +49,91 @@ export const useDialogueListStore = createGlobalState(() => {
const { index_name, records: _, ...updateProps } = dialogue
dialogueList.value[existingIndex] = {
...dialogueList.value[existingIndex],
...updateProps
...updateProps,
}
}
}
const updateDialogueRecord=(record)=>{
const updateDialogueRecord = (record) => {
const dialogue = lodash.cloneDeep(useDialogueStore())
const item = getDialogueList(dialogue.index_name)
const recordIndex = item.records.findIndex(item=>item.msg_id===record.msg_id)
if(recordIndex!==-1){
const recordIndex = item.records.findIndex(
(item) => item.msg_id === record.msg_id,
)
if (recordIndex !== -1) {
item.records[recordIndex] = {
...item.records[recordIndex],
...record
...record,
}
}
const virtualIndex = virtualList.value.findIndex(item=>item.msg_id===record.msg_id)
if(virtualIndex!==-1){
const virtualIndex = virtualList.value.findIndex(
(item) => item.msg_id === record.msg_id,
)
if (virtualIndex !== -1) {
virtualList.value[virtualIndex] = {
...virtualList.value[virtualIndex],
...record
...record,
}
}
}
const deleteDialogueRecord=(record)=>{
const deleteDialogueRecord = (record) => {
const dialogue = lodash.cloneDeep(useDialogueStore())
const item = getDialogueList(dialogue.index_name)
const recordIndex = item.records.findIndex(item=>item.msg_id===record.msg_id)
if(recordIndex!==-1){
item.records.splice(recordIndex,1)
const recordIndex = item.records.findIndex(
(item) => item.msg_id === record.msg_id,
)
if (recordIndex !== -1) {
item.records.splice(recordIndex, 1)
}
}
const updateUploadProgress=(id,progress)=>{
const record = virtualList.value.find(item=>item.msg_id===id)
if(record){
const updateUploadProgress = (id, progress) => {
const record = virtualList.value.find((item) => item.msg_id === id)
if (record) {
record.uploadCurrent = progress
}
}
const updateZpagingRef=(params)=>{
zpagingRef.value=params
const updateZpagingRef = (params) => {
zpagingRef.value = params
}
const zpagingComplete=(index_name)=>{
const zpagingComplete = (index_name) => {
const item = getDialogueList(index_name)
zpagingRef.value?.complete(lodash.cloneDeep(item.records).reverse())
}
const addChatRecord = (indexName,item)=>{
const addChatRecord = (indexName, item) => {
const dialogue = lodash.cloneDeep(useDialogueStore())
if (dialogue?.index_name === indexName) {
zpagingRef.value?.addChatRecordData(item,false,false)
zpagingRef.value?.addChatRecordData(item, false, false)
}
}
const batchDelDialogueRecord=(msgIds)=>{
const batchDelDialogueRecord = (msgIds) => {
const dialogue = lodash.cloneDeep(useDialogueStore())
const item = getDialogueList(dialogue.index_name)
item.records = item.records.filter(item=>!msgIds.includes(item.msg_id))
item.records = item.records.filter((item) => !msgIds.includes(item.msg_id))
}
//删除会话时同时刪除storage中存儲的會話
const delDialogueStorage = (indexName) => {
if (dialogueList?.value?.length > 0) {
dialogueList.value.forEach((item, index) => {
if (item?.index_name === indexName) {
dialogueList.value.splice(index, 1)
}
})
}
}
//清空聊天记录时,同时清空本地保存的聊天记录
const clearDialogueRecord = () => {
const dialogue = lodash.cloneDeep(useDialogueStore())
const item = getDialogueList(dialogue.index_name)
item.records = []
virtualList.value = []
}
return {
@ -122,5 +149,7 @@ export const useDialogueListStore = createGlobalState(() => {
addChatRecord,
virtualList,
batchDelDialogueRecord,
delDialogueStorage,
clearDialogueRecord,
}
})

View File

@ -1,9 +1,5 @@
import { defineStore } from 'pinia'
import {
ServeGroupDetail,
ServeGetGroupMembers,
ServeGetGroupNotices,
} from '@/api/group/index'
import { ServeGroupDetail, ServeGetGroupNotices } from '@/api/group/index'
import { useDialogueStore, useGroupTypeStore } from '@/store'
export const useGroupStore = defineStore('group', {
@ -32,25 +28,28 @@ export const useGroupStore = defineStore('group', {
if (code == 200) {
console.log(data)
let deptPosArr = []
if (data.deptInfos?.length > 0) {
// if (data.deptInfos?.length > 0) {
if (data.positionInfos?.length > 0) {
data.deptInfos.forEach((deptInfo) => {
data.positionInfos.forEach(async (positionInfo) => {
let deptPosItem = groupTypeStore.getDepartmentPositionsById(
deptInfo.dept_id,
positionInfo.position_id,
)
if (deptPosItem) {
// data.deptInfos.forEach((deptInfo) => {
data.positionInfos.forEach((positionInfo) => {
// let deptPosItem = groupTypeStore.getDepartmentPositionsById(
// deptInfo.dept_id,
// positionInfo.position_id,
// )
// if (deptPosItem) {
deptPosArr.push({
deptPos: deptPosItem,
dept_id: deptInfo.dept_id,
deptPos:
positionInfo.dept_name + '-' + positionInfo.position_name,
dept_id: positionInfo.dept_id,
dept_name: positionInfo.dept_name,
position_id: positionInfo.position_id,
position_name: positionInfo.position_name,
})
}
})
// }
})
// })
}
}
// }
data.groupAdminList = deptPosArr
this.groupInfo = data
}

View File

@ -10,7 +10,7 @@ export const useTalkStore = defineStore('talk', {
// 加载状态[1:未加载;2:加载中;3:加载完成;4:加载失败;]
loadStatus: 2,
// 会话列表
items: []
items: [],
}
},
getters: {
@ -26,14 +26,14 @@ export const useTalkStore = defineStore('talk', {
// 对话列表
talkItems: (state) => {
let topList = state.items.filter((item) => item.is_top == 1)
let listT = state.items.filter(v=>v.is_top !== 1)
let listT = state.items.filter((v) => v.is_top !== 1)
let listP = topList.sort((a, b) => {
return ttime(b.updated_at) - ttime(a.updated_at)
})
listT = listT.sort((a, b) => {
return ttime(b.updated_at) - ttime(a.updated_at)
})
return [...listP,...listT]
return [...listP, ...listT]
},
// 消息未读数总计
@ -41,7 +41,7 @@ export const useTalkStore = defineStore('talk', {
return state.items.reduce((total, item) => {
return total + item.unread_num
}, 0)
}
},
},
actions: {
findItem(index_name) {
@ -50,7 +50,9 @@ export const useTalkStore = defineStore('talk', {
// 更新对话节点
updateItem(params) {
const item = this.items.find((item) => item.index_name === params.index_name)
const item = this.items.find(
(item) => item.index_name === params.index_name,
)
item && Object.assign(item, params)
},
@ -73,7 +75,9 @@ export const useTalkStore = defineStore('talk', {
// 更新对话消息
updateMessage(params) {
const item = this.items.find((item) => item.index_name === params.index_name)
const item = this.items.find(
(item) => item.index_name === params.index_name,
)
if (item) {
item.unread_num++
item.msg_text = params.msg_text
@ -83,7 +87,9 @@ export const useTalkStore = defineStore('talk', {
// 更新联系人备注
setRemark(params) {
const item = this.items.find((item) => item.index_name === `1_${params.user_id}`)
const item = this.items.find(
(item) => item.index_name === `1_${params.user_id}`,
)
item && (item.remark = params.remark)
},
@ -126,34 +132,31 @@ export const useTalkStore = defineStore('talk', {
return this.items.findIndex((item) => item.index_name === index_name)
},
toTalk(talk_type, receiver_id, router) {
const route = {
path: '/message',
query: {
v: new Date().getTime()
}
}
toTalk(talk_type, receiver_id, erp_user_id) {
if (this.findTalkIndex(`${talk_type}_${receiver_id}`) >= 0) {
sessionStorage.setItem(KEY_INDEX_NAME, `${talk_type}_${receiver_id}`)
return router.push(route)
uni.reLaunch({
url: '/pages/index/index?openSessionIndexName=' + `${talk_type}_${receiver_id}`,
})
return
}
ServeCreateTalkList({
talk_type,
receiver_id
erp_user_id,
}).then(({ code, data, message }) => {
if (code == 200) {
if (this.findTalkIndex(`${talk_type}_${receiver_id}`) === -1) {
this.addItem(formatTalkItem(data))
}
sessionStorage.setItem(KEY_INDEX_NAME, `${talk_type}_${receiver_id}`)
return router.push(route)
uni.reLaunch({
url: '/pages/index/index?openSessionIndexName=' + `${talk_type}_${receiver_id}`,
})
} else {
message.warning(message)
}
})
}
}
},
},
})

View File

@ -54,7 +54,7 @@
<!-- #endif -->
<slot name="left"></slot>
</view>
<view class="flex flex-row-center-center" :style="{ width: contentwidth + 'px' }">
<view class="flex flex-row-center-center flex-col" :style="{ width: contentwidth + 'px' }">
<slot>
<tm-text
:unit="props.unit"
@ -63,6 +63,7 @@
:font-size="props.fontSize"
:label="_title"
></tm-text>
<slot name="subTitle"></slot>
</slot>
</view>
<view class="flex-row flex flex-row-center-end" :style="{ width: _rightWidth + 'rpx' }">

View File

@ -131,5 +131,23 @@
"groupNotice.quit.edit": "退出本次编辑",
"groupNotice.continue.edit": "继续编辑",
"groupNotice.confirm.quit": "退出",
"chatSettings.btn.removeAdmin": "移除"
"chatSettings.btn.removeAdmin": "移除",
"groupManage.disband.hint": "退出后,本群将被解散",
"groupManage.quit.hint": "退出后,聊天记录将被清空",
"index.mine.normal": "普通",
"select.member.remove": "选择要移除的人",
"select.member.num": "已选择",
"statistic.unit.person": "人",
"pageTitle.create.group": "发起群聊",
"pageTitle.select.department": "选择部门",
"radio.btn.selectAll": "全选",
"choose.deps.nextLevel": "下级",
"statistics.selected.deps": "已选择的部门数",
"chat.manage.addMembers": "添加群成员",
"pageTitle.select.groupType": "选择群类型",
"pageTitle.select.shareChat": "选择一个聊天",
"button.multiple.choice": "多选",
"button.text.close": "关闭",
"choose.deps.all": "全部",
"choose.deps.current": "当前"
}

View File

@ -1,74 +1,24 @@
## 2.7.112024-06-28
1.`新增` 方法`updateVirtualListRender`,支持手动触发虚拟列表渲染更新。
2.`新增` 延迟加载列表演示。
3.`修复` v2.7.8引出的vue3+npm+微信小程序中,`uni.$zp`配置失效的问题。
4.`修复` 在本地分页+虚拟列表情况下虚拟列表cell未被正常销毁的问题。
5.`修复` 打开调试模式下无法获取getApp导致的`cannot read property 'zp_handleQueryCallback' of undefined`的报错。
6.`修复` 极小概率出现的分页请求较快且快速滚动到底部时未能加载更多的问题。
## 2.7.102024-05-10
1.`修复` v2.7.8引出的vue3+npm+微信小程序中uni.$zp配置失效的问题。
## 2.7.92024-05-10
1.`修复` 在新版HbuilderX+vue3+微信小程序中可能出现的加载第二页数据后返回顶部无法下拉的问题。
2.`修复` 在vue3+抖音小程序中可能出现的首次加载reload没有触发的问题。
3.`优化` ts类型中`ZPagingInstance`泛型不必填,默认为`any`。
## 2.7.82024-05-09
1.`新增` typescript类型声明文件感谢小何同学提供。
2.`新增` 添加极简写法fetch相关配置及示例。
3.`新增` `fixed-cellHeight`配置支持在虚拟列表中自定义固定的cell高度。
4.`新增` `refresher-refreshing-scrollable`配置,支持自定义下拉刷新中是否允许列表滚动。
5.`修复` 在新版HubilderX+vue3+h5中`swiper-demo`模式`slot=top`被导航栏遮挡的问题。
6.`修复` 下拉进入二楼偶现的二楼高度未铺满全屏的问题。
7.`修复` 虚拟列表中complete若传相同对象会导致key重复的问题。
8.`修复` 在虚拟列表中调用refresh后缓存高度原始数据未清空的问题。
9.`修复` 聊天记录模式删除记录时顶部显示loading的问题。
9.`优化` `scrollIntoViewByIndex`支持在虚拟列表中滚动到指定cell。
10.`优化` `content-z-index`默认值修改为1。
## 2.7.72024-04-01
1.`新增` 下拉进入二楼功能及相关配置&demo。
2.`新增` 虚拟列表写法添加【非内置列表】写法可良好兼容vue3中的各平台并有较优的性能表现。
3.`新增` `z-paging-cell`补充`@touchstart`事件。
4.`修复` 页面滚动模式设置了`auto-full-height`后可能出现的依然有异常空白占位的问题和下拉刷新时列表数据被切割的问题。
## 2.7.62024-02-29
1.`新增` `max-width`,支持设置`z-paging`的最大宽度,默认`z-paging`宽度铺满窗口。
2.`新增` `chat-adjust-position-offset`,支持设置使用聊天记录模式中键盘弹出时占位高度偏移距离。
3.`修复` 由于renderjs中聊天记录模式判断不准确导致的可能出现的从聊天记录页面跳转到其他页面后返回页面无法滚动的问题。
4.`修复` 聊天记录模式首次加载失败后,发送消息顶部会显示加载失败文字的问题。
5.`修复` 聊天记录模式nvue可能出现的键盘弹出无法滚动到底部的问题。
6.`修复` 聊天记录模式+nvue滚动条无法展示的问题&底部会显示加载中的问题。
7.`修复` 聊天记录模式监听键盘弹出可能出现的无法正常销毁的问题。
8.`修复` 直接修改dataList的值组件内部的值未更新的问题。
## 2.7.52024-01-23
1.`新增` props`chat-loading-more-default-as-loading`,支持设置在聊天记录模式中滑动到顶部状态为默认状态时,以加载中的状态展示。
2.`新增` slots`chatNoMore`支持自定义聊天记录模式没有更多数据view。
3.`修复` 固定在底部view可能出现默认黄色的问题。
4.`优化` 聊天记录加载更多样式,与普通模式对齐,支持点击加载更多&点击重试,并支持加载更多相关配置。
5.`优化` 微调下拉刷新和底部加载更多样式。
6.`优化` 聊天记录模式自动滚动到底部添加延时以避免可能出现的滚动到底部位置不正确的问题。
7.`优化` 使用新的判断滚动到顶部算法以解决在安卓设备中可能出现的因滚动到顶部时scrollTop不为0导致的下拉刷新被禁用的问题。
## 2.7.42024-01-14
1.`新增` props:`auto-adjust-position-when-chat`支持设置使用聊天记录模式中键盘弹出时是否自动调整slot="bottom"高度。
2.`新增` props:`auto-to-bottom-when-chat`,支持设置使用聊天记录模式中键盘弹出时是否自动滚动到底部。
3.`新增` props:`show-chat-loading-when-reload`支持设置使用聊天记录模式中reload时是否显示chatLoading。
4.`修复` 在聊天记录模式中`scrollIntoViewById`和`scrollIntoViewByNodeTop`无效的问题。
5.`优化` 聊天记录模式底部安全区域针对键盘开启/关闭兼容处理。
6.`优化` 更新内置的空数据图&加载失败图感谢图鸟UI提供的免费可商用的空数据图和加载失败图
## 2.7.32024-01-10
1.`新增` 聊天记录模式支持虚拟列表&添加相关demo。
2.`新增` nvue中list添加`@scrollend`监听。
3.`优化` 聊天记录模式+vue第一页并且没有更多时不倒置列表。
4.`优化` 聊天记录模式+nvue中数据不满屏时默认从顶部开始不进行列表倒置。
## 2.7.22024-01-09
1.`修复` `vue3+h5`中报错`uni.onKeyboardHeightChange is not a function`的问题。
2.`优化` 聊天记录模式细节:表情面板在触摸列表时隐藏&添加隐藏动画。
## 2.7.12024-01-08
1.`新增` `keyboardHeightChange` event支持监听键盘高度改变。
2.`新增` 聊天记录模式新增切换表情面板/键盘demo。
3.`优化` 键盘弹出占位添加动画效果。
## 2.7.02024-01-07
2024新年快乐祝大家在新的一年里工作顺利事事顺心
1.`新增` 全新的聊天记录模式设计将vue中的聊天记录模式与nvue中对齐完全解决了聊天记录模式滚动到顶部加载更多在vue中抖动的问题同时将聊天记录模式键盘自动弹出自动上推页面交由`z-paging`处理,解决了由此引发的各种问题,尤其是在微信小程序中导航栏被键盘顶出屏幕外的问题。如果您使用了`z-paging`的聊天记录模式强烈建议更新写法有一定变更具体请参见demo。
2.`新增` `swiper-demo`新增`onShow`时候调用reload演示。
3.`修复` 修复滚动相关方法在微信小程序中首次滚动动画无效的问题。
4.`修复` props设置单位单位为px时报错的问题。
5.`修复` 在某些情况下`z-paging`加载了但是未渲染时reload无效的问题。
6.`修复` 底部loading动画未生效的问题。
## 2.8.42024-12-02
1.`修复` 在虚拟列表+vue2中顶部占位采用transformY方案在虚拟列表+vue3中顶部占位采用view占位方案。以解决在vue2+微信小程序+安卓+兼容模式中,可能出现的虚拟列表闪动的问题。
2.`修复` 在列表渲染时(尤其是在虚拟列表中)偶现的【点击加载更多】闪现的问题。
3.`优化` 统一在RefresherStatus枚举中Loading取值。
4.`优化` `defaultPageNo`&`defaultPageSize`修改为只允许number类型。
5.`优化` 提升兼容性&细节优化。
## 2.8.32024-11-27
1.`修复` `doInsertVirtualListItem`插入数据无效的问题。
2.`优化` 提升兼容性&细节优化。
## 2.8.22024-11-25
1.`优化` types中`ZPagingRef`和`ZPagingInstance`支持泛型。
## 2.8.12024-11-24
1.`新增` 完整的`props`、`slots`、`methods`、`events`的typescript types声明可在ts中获得绝佳的代码提示体验。
2.`新增` `virtual-cell-id-prefix`虚拟列表cell id的前缀适用于一个页面有多个虚拟列表的情况用以区分不同虚拟列表cell的id。
3.`修复` 在vue3+(微信小程序或QQ小程序)中,使用非内置列表写法时,若`z-paging`在`swiper-item`标签内的情况下存在的无法获取slot插入的cell高度的问题。
4.`修复` 在虚拟列表中分页数据小于1页时插入新数据虚拟列表未生效的问题。
5.`修复` 在虚拟列表中调用`refresh`时cell的index计算不正确的问题。
6.`修复` 在快手小程序中内容较少或空数据时`z-paging`未能铺满全屏的问题。
7.`优化` `events`中的参数涉及枚举的部分统一由之前的number类型修改为string类型展示更直观涉及的events`@query`中的`from`参数;`@refresherStatusChange`中的`status`参数;`@loadingStatusChange`中的`status`参数;`slot=refresher`中的`refresherStatus`参数;`slot=chatLoading`中的`loadingMoreStatus`参数。更新版本请特别留意!
## 2.8.02024-10-21
1.`新增` 全面支持鸿蒙Next。
2.`修复` 设置了`refresher-complete-delay`后在下拉刷新期间调用reload导致的无法再次下拉刷新的问题。
3.`优化` 废弃虚拟列表transformY顶部占位方案修改为空view占位。解决因使用旧方案导致的vue3中可能出现的虚拟列表闪动问题。提升虚拟列表的兼容性。

View File

@ -55,8 +55,12 @@
},
//
ownLoadingMoreText() {
const statusTextArr = [this.c.defaultText,this.c.loadingText,this.c.noMoreText,this.c.failText];
return statusTextArr[this.finalStatus];
return {
[this.M.Default]: this.c.defaultText,
[this.M.Loading]: this.c.loadingText,
[this.M.NoMore]: this.c.noMoreText,
[this.M.Fail]: this.c.failText,
}[this.finalStatus];
},
//
finalStatus() {

View File

@ -57,14 +57,21 @@
ts() {
return this.defaultThemeStyle;
},
//
statusTextArr() {
// Map
statusTextMap() {
this.updateTime();
return [this.defaultText, this.pullingText, this.refreshingText, this.completeText, this.goF2Text];
const { R, defaultText, pullingText, refreshingText, completeText, goF2Text } = this;
return {
[R.Default]: defaultText,
[R.ReleaseToRefresh]: pullingText,
[R.Loading]: refreshingText,
[R.Complete]: completeText,
[R.GoF2]: goF2Text,
};
},
//
currentTitle() {
return this.statusTextArr[this.status] || this.defaultText;
return this.statusTextMap[this.status] || this.defaultText;
},
// class
leftImageClass() {

View File

@ -193,6 +193,10 @@
z-index: 999;
}
.zp-back-to-top-img-inversion {
transform: rotate(180deg);
}
.zp-empty-view {
/* #ifdef APP-NVUE */
height: 100%;

View File

@ -89,10 +89,10 @@ export default {
!callbacked && this._handleToTop();
})
},
// 处理滚动到顶部
// 处理滚动到顶部(聊天记录模式中为滚动到底部)
_handleToTop() {
!this.backToTopWithAnimate && this._checkShouldShowBackToTop(0);
this.scrollToTop(this.backToTopWithAnimate);
!this.useChatRecordMode ? this.scrollToTop(this.backToTopWithAnimate) : this.scrollToBottom(this.backToTopWithAnimate);
},
// 判断是否要显示返回顶部按钮
_checkShouldShowBackToTop(scrollTop) {

View File

@ -82,7 +82,7 @@ export default {
})
},
// 获取节点尺寸
_getNodeClientRect(select, inDom = true, scrollOffset = false) {
_getNodeClientRect(select, inDom = true, scrollOffset = false, inParent = false) {
if (this.isReadyDestroy) {
return Promise.resolve(false);
};
@ -106,7 +106,8 @@ export default {
//#ifdef MP-ALIPAY
inDom = false;
//#endif
let res = !!inDom ? uni.createSelectorQuery().in(inDom === true ? this : inDom) : uni.createSelectorQuery();
const inDomObj = inParent ? this.$parent : this;
let res = inDom || inParent ? uni.createSelectorQuery().in(inDomObj) : uni.createSelectorQuery();
scrollOffset ? res.select(select).scrollOffset() : res.select(select).boundingClientRect();
return new Promise((resolve, reject) => {
res.exec(data => {

View File

@ -8,7 +8,7 @@ export default {
props: {
// 自定义初始的pageNo默认为1
defaultPageNo: {
type: [Number, String],
type: Number,
default: u.gc('defaultPageNo', 1),
observer: function(newVal) {
this.pageNo = newVal;
@ -16,7 +16,7 @@ export default {
},
// 自定义pageSize默认为10
defaultPageSize: {
type: [Number, String],
type: Number,
default: u.gc('defaultPageSize', 10),
validator: (value) => {
if (value <= 0) u.consoleErr('default-page-size必须大于0');
@ -473,14 +473,10 @@ export default {
if (!isLocal && tempIsUserPullDown && this.isFirstPage) {
this.isUserPullDown = false;
}
if (!this.isFirstPage) {
this.listRendering = true;
this.$nextTick(() => {
u.delay(() => this.listRendering = false);
})
} else {
this.listRendering = false;
}
let dataTypeRes = this._checkDataType(data, success, isLocal);
data = dataTypeRes.data;
success = dataTypeRes.success;
@ -511,7 +507,9 @@ export default {
const localPageNo = this.defaultPageNo;
const localPageSize = this.queryFrom !== Enum.QueryFrom.Refresh ? this.defaultPageSize : this.currentRefreshPageSize;
this._localPagingQueryList(localPageNo, localPageSize, 0, res => {
this.completeByTotal(res, this.totalLocalPagingList.length);
u.delay(() => {
this.completeByTotal(res, this.totalLocalPagingList.length);;
}, 0)
})
} else {
// 如果当前不是本地分页,则按照正常分页逻辑进行数据处理&emit数据
@ -538,7 +536,7 @@ export default {
this._callDataPromise(false);
this.loadingStatus = Enum.More.Fail;
this.isHandlingRefreshToPage = false;
if (this.loadingType === Enum.LoadingType.LoadingMore) {
if (this.loadingType === Enum.LoadingType.LoadMore) {
this.pageNo --;
}
}

View File

@ -179,12 +179,16 @@ export default {
// 是否显示自定义状态下的底部加载更多
showLoadingMoreCustom() {
return this._showLoadingMore('Custom');
}
},
// 底部加载更多固定高度
loadingMoreFixedHeight() {
return u.addUnit('80rpx', this.unit);
},
},
methods: {
// 页面滚动到底部时通知z-paging进行进一步处理
pageReachBottom() {
!this.useChatRecordMode && this._onLoadingMore('toBottom');
!this.useChatRecordMode && this.toBottomLoadingMoreEnabled && this._onLoadingMore('toBottom');
},
// 手动触发上拉加载更多(非必须,可依据具体需求使用)
doLoadMore(type) {
@ -275,15 +279,15 @@ export default {
// 如果是本地分页,则在组件内部对数据进行分页处理,不触发@query事件
this._localPagingQueryList(this.pageNo, this.defaultPageSize, this.localPagingLoadingTime, res => {
this.completeByTotal(res, this.totalLocalPagingList.length);
this.queryFrom = Enum.QueryFrom.LoadingMore;
this.queryFrom = Enum.QueryFrom.LoadMore;
})
} else {
// emit @query相关加载更多事件
this._emitQuery(this.pageNo, this.defaultPageSize, Enum.QueryFrom.LoadingMore);
this._emitQuery(this.pageNo, this.defaultPageSize, Enum.QueryFrom.LoadMore);
this._callMyParentQuery();
}
// 设置当前加载状态为底部加载更多状态
this.loadingType = Enum.LoadingType.LoadingMore;
this.loadingType = Enum.LoadingType.LoadMore;
}
},
// (预处理)判断当没有更多数据且分页内容未超出z-paging时是否显示没有更多数据的view

View File

@ -66,7 +66,7 @@ export default {
nLoadingMoreFixedHeight: false,
nShowRefresherRevealHeight: 0,
nOldShowRefresherRevealHeight: -1,
nRefresherWidth: uni.upx2px(750),
nRefresherWidth: u.rpx2px(750),
nF2Opacity: 0
}
},

View File

@ -349,7 +349,7 @@ export default {
return this.refresherTriggered;
},
showRefresher() {
const showRefresher = this.finalRefresherEnabled && this.useCustomRefresher;
const showRefresher = this.finalRefresherEnabled || this.useCustomRefresher && !this.useChatRecordMode;
// #ifndef APP-NVUE
this.active && this.customRefresherHeight === -1 && showRefresher && this.updateCustomRefresherHeight();
// #endif
@ -415,7 +415,7 @@ export default {
this.$emit('onRestore');
this.$emit('Restore');
},
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
// #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5
// touch开始
_refresherTouchstart(e) {
this._handleListTouchstart();
@ -439,8 +439,8 @@ export default {
this._cleanRefresherEndTimeout();
},
// 非appvue或微信小程序或QQ小程序或h5平台使用js控制下拉刷新
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
// 非app-ios/android或微信小程序或QQ小程序或h5平台使用js控制下拉刷新
// #ifndef APP-IOS || APP-ANDROID || MP-WEIXIN || MP-QQ || H5
// touch中
_refresherTouchmove(e) {
const currentTimeStamp = u.getTime();
@ -515,7 +515,7 @@ export default {
// 下拉刷新距离未超过阈值,显示默认状态
this.refresherStatus = Enum.Refresher.Default;
}
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
// #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5
// this.scrollEnable = false;
// 通过transform控制下拉刷新view垂直偏移
this.refresherTransform = `translateY(${moveDis}px)`;
@ -523,7 +523,7 @@ export default {
// #endif
this.moveDis = moveDis;
},
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
// #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5
// touch结束
_refresherTouchend(e) {
// 下拉刷新用户手离开屏幕,允许列表滚动
@ -553,7 +553,7 @@ export default {
this._refresherEnd();
} else {
// 如果是松手立即刷新状态,则触发下拉刷新
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
// #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5
this.refresherTransform = `translateY(${refresherThreshold}px)`;
this.refresherTransition = 'transform .1s linear';
// #endif
@ -584,7 +584,7 @@ export default {
_handleScrollViewBounce({ bounce }) {
if (!this.usePageScroll && !this.scrollToTopBounceEnabled) {
if (this.wxsScrollTop <= 5) {
// #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5
// #ifdef APP-PLUS || MP-WEIXIN || MP-QQ || H5
this.refresherTransition = '';
// #endif
this.scrollEnable = bounce;
@ -615,7 +615,9 @@ export default {
// 下拉刷新结束
_refresherEnd(shouldEndLoadingDelay = true, fromAddData = false, isUserPullDown = false, setLoading = true) {
if (this.loadingType === Enum.LoadingType.Refresher) {
// 计算当前下拉刷新结束需要延迟的时间
const refresherCompleteDelay = (fromAddData && (isUserPullDown || this.showRefresherWhenReload)) ? this.refresherCompleteDelay : 0;
// 如果延迟时间大于0则展示刷新结束状态否则直接展示默认状态
const refresherStatus = refresherCompleteDelay > 0 ? Enum.Refresher.Complete : Enum.Refresher.Default;
if (this.finalShowRefresherWhenReload) {
const stackCount = this.refresherRevealStackCount;
@ -624,7 +626,12 @@ export default {
}
this._cleanRefresherEndTimeout();
this.refresherEndTimeout = u.delay(() => {
// 更新下拉刷新状态
this.refresherStatus = refresherStatus;
// 如果当前下拉刷新状态不是刷新结束,则认为其不在刷新结束状态
if (refresherStatus !== Enum.Refresher.Complete) {
this.isRefresherInComplete = false;
}
}, this.refresherStatus !== Enum.Refresher.Default && refresherStatus === Enum.Refresher.Default ? this.refresherCompleteDuration : 0);
// #ifndef APP-NVUE
@ -640,11 +647,11 @@ export default {
animateDuration = this.refresherEndBounceEnabled ? this.refresherCompleteDuration / 1000 : this.refresherCompleteDuration / 3000;
}
this.refresherTransition = `transform ${fromAddData ? animateDuration : this.refresherDefaultDuration / 1000}s ${animateType}`;
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
// #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5
this.refresherTransform = 'translateY(0px)';
this.currentDis = 0;
// #endif
// #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5
// #ifdef APP-PLUS || MP-WEIXIN || MP-QQ || H5
this.wxsPropType = this.refresherTransition + 'end' + u.getTime();
// #endif
// #ifdef APP-NVUE
@ -748,10 +755,10 @@ export default {
}
// #endif
this.refresherRevealStackCount ++;
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
// #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5
this.refresherTransform = `translateY(${this.finalRefresherThreshold}px)`;
// #endif
// #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5
// #ifdef APP-PLUS || MP-WEIXIN || MP-QQ || H5
this.wxsPropType = 'begin' + u.getTime();
// #endif
this.moveDis = this.finalRefresherThreshold;
@ -762,10 +769,10 @@ export default {
},
// 触发下拉刷新
_doRefresherLoad(isUserPullDown = true) {
this._onRefresh(false,isUserPullDown);
this._onRefresh(false, isUserPullDown);
this.loading = true;
},
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
// #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5
// 获取处理后的moveDis
_getFinalRefresherMoveDis(moveDis) {
let diffDis = moveDis - this.oldCurrentMoveDis;

View File

@ -240,7 +240,9 @@ export default {
},
// 当滚动到底部时
_onScrollToLower(e) {
(!e.detail || !e.detail.direction || e.detail.direction === 'bottom') && this._onLoadingMore(this.useChatRecordMode ? 'click' : 'toBottom')
(!e.detail || !e.detail.direction || e.detail.direction === 'bottom')
&& this.toBottomLoadingMoreEnabled
&& this._onLoadingMore(this.useChatRecordMode ? 'click' : 'toBottom')
},
// 滚动到顶部
_scrollToTop(animate = true, isPrivate = true) {

View File

@ -74,6 +74,11 @@ export default {
type: [Number, String],
default: u.gc('virtualScrollFps', 80)
},
// 虚拟列表cell id的前缀适用于一个页面有多个虚拟列表的情况用以区分不同虚拟列表cell的id注意请勿传数字或以数字开头的字符串。如设置为list1则cell的id应为list1-zp-id-${item.zp_index}
virtualCellIdPrefix: {
type: String,
default: u.gc('virtualCellIdPrefix', '')
},
},
data() {
return {
@ -110,6 +115,10 @@ export default {
virtualList(newVal){
this.$emit('update:virtualList', newVal);
this.$emit('virtualListChange', newVal);
},
// 监听虚拟列表顶部占位高度改变并emit
virtualPlaceholderTopHeight(newVal) {
this.$emit('virtualTopHeightChange', newVal);
}
},
computed: {
@ -139,17 +148,31 @@ export default {
finalFixedCellHeight() {
return u.convertToPx(this.fixedCellHeight);
},
fianlVirtualCellIdPrefix() {
const prefix = this.virtualCellIdPrefix ? this.virtualCellIdPrefix + '-' : '';
return prefix + 'zp-id';
},
finalPlaceholderTopHeightStyle() {
// #ifdef VUE2
return { transform: this.virtualPlaceholderTopHeight > 0 ? `translateY(${this.virtualPlaceholderTopHeight}px)` : 'none' };
// #endif
return {};
},
virtualRangePageHeight(){
return this.finalVirtualPageHeight * this.preloadPage;
},
virtualScrollDisTimeStamp() {
return 1000 / this.virtualScrollFps;
},
}
},
methods: {
// 在使用动态高度虚拟列表时若在列表数组中需要插入某个item需要调用此方法item:需要插入的itemindex:插入的cell位置若index为2则插入的item在原list的index=1之后index从0开始
doInsertVirtualListItem(item, index) {
if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return;
this.realTotalData.splice(index, 0, item);
// #ifdef VUE3
this.realTotalData = [...this.realTotalData];
// #endif
this.virtualItemInsertedCount ++;
if (!item || Object.prototype.toString.call(item) !== '[object Object]') {
item = { item };
@ -162,7 +185,7 @@ export default {
while (retryCount <= 10) {
await u.wait(c.delayTime);
const cellNode = await this._getNodeClientRect(`#zp-id-${item[cellIndexKey]}`, this.finalUseInnerList);
const cellNode = await this._getVirtualCellNodeByIndex(item[cellIndexKey]);
// 如果获取当前cell的节点信息失败则重试不超过10次
if (!cellNode) {
retryCount ++;
@ -191,12 +214,12 @@ export default {
}
})
},
// 在使用动态高度虚拟列表时手动更新指定cell的缓存高度(当cell高度在初始化之后再次改变调用)index:需要更新的cell在列表中的位置从0开始
// 在使用动态高度虚拟列表时手动更新指定cell的缓存高度(当cell高度在初始化之后再次改变调用)index:需要更新的cell在列表中的位置从0开始
didUpdateVirtualListCell(index) {
if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return;
const currentNode = this.virtualHeightCacheList[index];
this.$nextTick(() => {
this._getNodeClientRect(`#zp-id-${index}`, this.finalUseInnerList).then(cellNode => {
this._getVirtualCellNodeByIndex(index).then(cellNode => {
// 更新当前cell的高度
const cellNodeHeight = cellNode ? cellNode[0].height : 0;
const heightDis = cellNodeHeight - currentNode.height;
@ -261,7 +284,7 @@ export default {
if (!this.finalFixedCellHeight) {
this.$nextTick(() => {
u.delay(() => {
this._getNodeClientRect(`#zp-id-${0}`,this.finalUseInnerList).then(cellNode => {
this._getVirtualCellNodeByIndex(0).then(cellNode => {
if (!cellNode) {
if (this.getCellHeightRetryCount.fixed > 10) return;
this.getCellHeightRetryCount.fixed ++;
@ -287,7 +310,7 @@ export default {
this.$nextTick(() => {
u.delay(async () => {
for (let i = 0; i < list.length; i++) {
const cellNode = await this._getNodeClientRect(`#zp-id-${list[i][this.virtualCellIndexKey]}`, this.finalUseInnerList);
const cellNode = await this._getVirtualCellNodeByIndex(list[i][this.virtualCellIndexKey]);
const currentHeight = cellNode ? cellNode[0].height : 0;
if (!cellNode) {
if (this.getCellHeightRetryCount.dynamic <= 10) {
@ -328,8 +351,8 @@ export default {
_setCellIndex(list, dataFrom = 'bottom') {
let currentItemIndex = 0;
const cellIndexKey = this.virtualCellIndexKey;
([Enum.QueryFrom.Refresh, Enum.QueryFrom.Reload].indexOf(this.queryFrom) >= 0) && this._resetDynamicListState();
if (this.totalData.length) {
dataFrom === 'bottom' && ([Enum.QueryFrom.Refresh, Enum.QueryFrom.Reload].indexOf(this.queryFrom) >= 0) && this._resetDynamicListState();
if (this.totalData.length && this.queryFrom !== Enum.QueryFrom.Refresh) {
if (dataFrom === 'bottom') {
currentItemIndex = this.realTotalData.length;
const lastItem = this.realTotalData.length ? this.realTotalData.slice(-1)[0] : null;
@ -504,6 +527,20 @@ export default {
})
}
},
// 获取对应index的虚拟列表cell节点信息
_getVirtualCellNodeByIndex(index) {
let inParent = false;
// 在vue3+(微信小程序或QQ小程序)中使用非内置列表写法时若z-paging在swiper-item内存在无法获取slot插入的cell高度的问题
// 通过uni.createSelectorQuery().in(this.$parent)来解决此问题
// #ifdef VUE3
// #ifdef MP-WEIXIN || MP-QQ
if (this.forceCloseInnerList) {
inParent = true;
}
// #endif
// #endif
return this._getNodeClientRect(`#${this.fianlVirtualCellIdPrefix}-${index}`, this.finalUseInnerList, false, inParent);
},
// 处理使用内置列表时点击了cell事件
_innerCellClick(item, index) {
this.$emit('innerCellClick', item, index);

View File

@ -2,7 +2,7 @@
export default {
// 当前版本号
version: '2.7.11',
version: '2.8.3',
// 延迟操作的通用时间
delayTime: 100,
// 请求失败时候全局emit使用的key

View File

@ -1,32 +1,32 @@
// [z-paging]枚举
export default {
// 当前加载类型 0.下拉刷新 1.上拉加载更多
// 当前加载类型 refresher:下拉刷新 load-more:上拉加载更多
LoadingType: {
Refresher: 0,
LoadingMore: 1
Refresher: 'refresher',
LoadMore: 'load-more'
},
// 下拉刷新状态 0.默认状态 1.松手立即刷新 2.刷新中 3.刷新结束 4.松手进入二楼
// 下拉刷新状态 default:默认状态 release-to-refresh:松手立即刷新 loading:刷新中 complete:刷新结束 go-f2:松手进入二楼
Refresher: {
Default: 0,
ReleaseToRefresh: 1,
Loading: 2,
Complete: 3,
GoF2: 4
Default: 'default',
ReleaseToRefresh: 'release-to-refresh',
Loading: 'loading',
Complete: 'complete',
GoF2: 'go-f2'
},
// 底部加载更多状态 0.默认状态 1.加载中 2.没有更多数据 3.加载失败
// 底部加载更多状态 default:默认状态 loading:加载中 no-more:没有更多数据 fail:加载失败
More: {
Default: 0,
Loading: 1,
NoMore: 2,
Fail: 3
Default: 'default',
Loading: 'loading',
NoMore: 'no-more',
Fail: 'fail'
},
// @query触发来源 0.用户主动下拉刷新 1.通过reload触发 2.通过refresh触发 3.通过滚动到底部加载更多或点击底部加载更多触发
// @query触发来源 user-pull-down:用户主动下拉刷新 reload:通过reload触发 refresh:通过refresh触发 load-more:通过滚动到底部加载更多或点击底部加载更多触发
QueryFrom: {
UserPullDown: 0,
Reload: 1,
Refresh: 2,
LoadingMore: 3
UserPullDown: 'user-pull-down',
Reload: 'reload',
Refresh: 'refresh',
LoadMore: 'load-more'
},
// 虚拟列表cell高度模式
CellHeightMode: {

View File

@ -50,8 +50,6 @@ export default {
data() {
return {
// --------------静态资源---------------
base64Arrow: zStatic.base64Arrow,
base64Flower: zStatic.base64Flower,
base64BackToTop: zStatic.base64BackToTop,
// -------------全局数据相关--------------
@ -219,6 +217,9 @@ export default {
this.systemInfo = uni.getSystemInfoSync();
// 初始化z-paging高度
!this.usePageScroll && this.autoHeight && this._setAutoHeight();
// #ifdef MP-KUAISHOU
this._setFullScrollViewInHeight();
// #endif
this.loaded = true;
u.delay(() => {
// 更新fixed模式下z-paging的布局主要是更新windowTop、windowBottom
@ -408,10 +409,7 @@ export default {
},
// 设置z-paging高度
async _setAutoHeight(shouldFullHeight = true, scrollViewNode = null) {
let heightKey = 'min-height';
// #ifndef APP-NVUE
heightKey = 'min-height';
// #endif
const heightKey = 'min-height';
try {
if (shouldFullHeight) {
// 如果需要铺满全屏,则计算当前全屏可是区域的高度
@ -432,6 +430,16 @@ export default {
}
} catch (e) {}
},
// #ifdef MP-KUAISHOU
// 设置scroll-view内容器的最小高度等于scroll-view的高度(为了解决在快手小程序中内容较少时scroll-view内容器高度无法铺满scroll-view的问题)
_setFullScrollViewInHeight() {
try {
// 如果需要铺满全屏,则计算当前全屏可是区域的高度
const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view');
scrollViewNode && this.$set(this.scrollViewInStyle, 'min-height', scrollViewNode[0].height + 'px');
} catch (e) {}
},
// #endif
// 组件销毁后续处理
_handleUnmounted() {
this.active = false;

View File

@ -8,6 +8,10 @@ let config = null;
let configLoaded = false;
const timeoutMap = {};
// #ifdef APP-HARMONY
let screenWidth = 0;
// #endif
// 获取默认配置信息
function gc(key, defaultValue) {
// 这里return一个函数以解决在vue3+appvue中props默认配置读取在main.js之前执行导致uni.$zp全局配置无效的问题。相当于props的default中传入一个带有返回值的函数
@ -119,12 +123,25 @@ function convertToPx(text) {
text = text.replace('px', '');
}
if (!isNaN(text)) {
if (isRpx) return Number(uni.upx2px(text));
if (isRpx) return Number(rpx2px(text));
return Number(text);
}
return 0;
}
// rpx => px兼容鸿蒙
function rpx2px(rpx) {
// #ifdef APP-HARMONY
if (!screenWidth) {
screenWidth = uni.getSystemInfoSync().screenWidth;
}
return (screenWidth * Number.parseFloat(rpx)) / 750;
// #endif
// #ifndef APP-HARMONY
return uni.upx2px(rpx);
// #endif
}
// 获取当前时间
function getTime() {
return (new Date()).getTime();
@ -266,5 +283,6 @@ export default {
wait,
isPromise,
addUnit,
deepCopy
deepCopy,
rpx2px
};

View File

@ -4,13 +4,13 @@
/ /_____| |_) | (_| | (_| | | | | | (_| |
/___| | .__/ \__,_|\__, |_|_| |_|\__, |
|_| |___/ |___/
v2.7.11 (2024-06-28)
by ZXLee
v2.8.3 (2024-11-27)
@author ZXLee <admin@zxlee.cn>
-->
<!-- 文档地址https://z-paging.zxlee.cn -->
<!-- github地址https://github.com/SmileZXLee/uni-z-paging -->
<!-- dcloud地址https://ext.dcloud.net.cn/plugin?id=3935 -->
<!-- 反馈QQ群790460711(已满)371624008 -->
<!-- 反馈QQ群343409055 -->
<template name="z-paging">
<!-- #ifndef APP-NVUE -->
@ -44,17 +44,17 @@ by ZXLee
@scrolltoupper="_onScrollToUpper" @refresherrestore="_onRestore" @refresherrefresh="_onRefresh(true)"
>
<view class="zp-paging-touch-view"
<!-- #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 -->
<!-- #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5 -->
@touchstart="_refresherTouchstart" @touchmove="_refresherTouchmove" @touchend="_refresherTouchend" @touchcancel="_refresherTouchend"
<!-- #endif -->
<!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 -->
<!-- #ifdef APP-PLUS || MP-WEIXIN || MP-QQ || H5 -->
@touchstart="pagingWxs.touchstart" @touchmove="pagingWxs.touchmove" @touchend="pagingWxs.touchend" @touchcancel="pagingWxs.touchend"
@mousedown="pagingWxs.mousedown" @mousemove="pagingWxs.mousemove" @mouseup="pagingWxs.mouseup" @mouseleave="pagingWxs.mouseleave"
<!-- #endif -->
>
<view v-if="finalRefresherFixedBacHeight>0" class="zp-fixed-bac-view" :style="[{'background': refresherFixedBackground,'height': `${finalRefresherFixedBacHeight}px`}]"></view>
<view class="zp-paging-main" :style="[scrollViewInStyle,{'transform': finalRefresherTransform,'transition': refresherTransition}]"
<!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 -->
<!-- #ifdef APP-PLUS || MP-WEIXIN || MP-QQ || H5 -->
:change:prop="pagingWxs.propObserver" :prop="wxsPropType"
:data-refresherThreshold="finalRefresherThreshold" :data-refresherF2Enabled="refresherF2Enabled" :data-refresherF2Threshold="finalRefresherF2Threshold" :data-isIos="isIos"
:data-loading="loading||isRefresherInComplete" :data-useChatRecordMode="useChatRecordMode"
@ -63,7 +63,7 @@ by ZXLee
:data-refresherAecc="refresherAngleEnableChangeContinued" :data-usePageScroll="usePageScroll" :data-watchTouchDirectionChange="watchTouchDirectionChange"
:data-oldIsTouchmoving="isTouchmoving" :data-refresherOutRate="finalRefresherOutRate" :data-refresherPullRate="finalRefresherPullRate" :data-hasTouchmove="hasTouchmove"
<!-- #endif -->
<!-- #ifdef APP-VUE || H5 -->
<!-- #ifdef APP-PLUS || H5 -->
:change:renderPropIsIosAndH5="pagingRenderjs.renderPropIsIosAndH5Change" :renderPropIsIosAndH5="isIosAndH5"
<!-- #endif -->
>
@ -88,15 +88,19 @@ by ZXLee
<!-- 全屏Loading -->
<slot v-if="showLoading&&zSlots.loading&&!loadingFullFixed" name="loading" />
<!-- 主体内容 -->
<view class="zp-paging-container-content" :style="[{transform:virtualPlaceholderTopHeight>0?`translateY(${virtualPlaceholderTopHeight}px)`:'none'},finalPagingContentStyle]">
<view class="zp-paging-container-content" :style="[finalPlaceholderTopHeightStyle,finalPagingContentStyle]">
<!-- #ifdef VUE3 -->
<!-- 虚拟列表顶部占位view -->
<view v-if="useVirtualList" class="zp-virtual-placeholder" :style="[{height:virtualPlaceholderTopHeight+'px'}]"/>
<!-- #endif -->
<slot />
<!-- 内置列表&虚拟列表 -->
<template v-if="finalUseInnerList">
<slot name="header"/>
<view class="zp-list-container" :style="[innerListStyle]">
<template v-if="finalUseVirtualList">
<view class="zp-list-cell" :style="[innerCellStyle]" :id="`zp-id-${item[virtualCellIndexKey]}`" v-for="(item,index) in virtualList" :key="item['zp_unique_index']" @click="_innerCellClick(item,virtualTopRangeIndex+index)">
<view v-if="useCompatibilityMode">使用兼容模式请在组件源码z-paging.vue第99行中注释这一行并打开下面一行注释</view>
<view class="zp-list-cell" :style="[innerCellStyle]" :id="`${fianlVirtualCellIdPrefix}-${item[virtualCellIndexKey]}`" v-for="(item,index) in virtualList" :key="item['zp_unique_index']" @click="_innerCellClick(item,virtualTopRangeIndex+index)">
<view v-if="useCompatibilityMode">使用兼容模式请在组件源码z-paging.vue第103行中注释这一行并打开下面一行注释</view>
<!-- <zp-public-virtual-cell v-if="useCompatibilityMode" :extraData="extraData" :item="item" :index="virtualTopRangeIndex+index" /> -->
<slot v-else name="cell" :item="item" :index="virtualTopRangeIndex+index"/>
</view>
@ -170,7 +174,7 @@ by ZXLee
<!-- 点击返回顶部view -->
<view v-if="showBackToTopClass" :class="finalBackToTopClass" :style="[finalBackToTopStyle]" @click.stop="_backToTopClick">
<slot v-if="zSlots.backToTop" name="backToTop" />
<image v-else class="zp-back-to-top-img" :src="backToTopImg.length?backToTopImg:base64BackToTop" />
<image v-else class="zp-back-to-top-img" :class="{'zp-back-to-top-img-inversion': useChatRecordMode&&!backToTopImg.length}" :src="backToTopImg.length?backToTopImg:base64BackToTop" />
</view>
<!-- 全屏Loading(铺满z-paging并固定) -->
<view v-if="showLoading&&zSlots.loading&&loadingFullFixed" class="zp-loading-fixed">
@ -261,7 +265,7 @@ by ZXLee
</view>
</template>
<view :style="nLoadingMoreFixedHeight?{height:loadingMoreCustomStyle&&loadingMoreCustomStyle.height?loadingMoreCustomStyle.height:'80rpx'}:{}">
<view :style="nLoadingMoreFixedHeight?{height:loadingMoreCustomStyle&&loadingMoreCustomStyle.height?loadingMoreCustomStyle.height:loadingMoreFixedHeight}:{}">
<slot v-if="showLoadingMoreDefault" name="loadingMoreDefault" />
<slot v-else-if="showLoadingMoreLoading" name="loadingMoreLoading" />
<slot v-else-if="showLoadingMoreNoMore" name="loadingMoreNoMore" />
@ -298,7 +302,7 @@ by ZXLee
<!-- 点击返回顶部view -->
<view v-if="showBackToTopClass" :class="finalBackToTopClass" :style="[finalBackToTopStyle]" @click.stop="_backToTopClick">
<slot v-if="zSlots.backToTop" name="backToTop" />
<image v-else class="zp-back-to-top-img" :src="backToTopImg.length?backToTopImg:base64BackToTop" />
<image v-else class="zp-back-to-top-img" :class="{'zp-back-to-top-img-inversion': useChatRecordMode&&!backToTopImg.length}" :src="backToTopImg.length?backToTopImg:base64BackToTop" />
</view>
<!-- 全屏Loading(铺满z-paging并固定) -->
<view v-if="showLoading&&zSlots.loading&&loadingFullFixed" class="zp-loading-fixed">
@ -307,9 +311,12 @@ by ZXLee
</component>
<!-- #endif -->
</template>
<!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 -->
<!-- #ifndef APP-NVUE -->
<!-- #ifdef APP-PLUS || MP-WEIXIN || MP-QQ || H5 -->
<script src="./wxs/z-paging-wxs.wxs" module="pagingWxs" lang="wxs"></script>
<!-- #endif -->
<!-- #endif -->
<script module="pagingRenderjs" lang="renderjs">
import pagingRenderjs from './wxs/z-paging-renderjs.js';
/**
@ -332,9 +339,11 @@ by ZXLee
*/
export default {
name:"z-paging",
// #ifdef APP-VUE || H5
// #ifndef APP-NVUE
// #ifdef APP-PLUS || H5
mixins: [pagingRenderjs],
// #endif
// #endif
}
</script>
<script src="./js/z-paging-main.js" />

View File

@ -2,8 +2,8 @@
"id": "z-paging",
"name": "z-paging",
"displayName": "【z-paging下拉刷新、上拉加载】高性能全平台兼容。支持虚拟列表分页全自动处理",
"version": "2.7.11",
"description": "超简单、低耦合使用wxs+renderjs实现。支持自定义下拉刷新、上拉加载更多、虚拟列表、下拉进入二楼、自动管理空数据图、无闪动聊天分页、本地分页、国际化等100+项配置",
"version": "2.8.4",
"description": "超简单、低耦合使用wxs+renderjs实现。支持自定义下拉刷新、上拉加载更多、虚拟列表、下拉进入二楼、自动管理空数据图、无闪动聊天分页、本地分页、国际化等数百项配置",
"keywords": [
"下拉刷新",
"上拉加载",
@ -12,7 +12,6 @@
"虚拟列表"
],
"repository": "https://github.com/SmileZXLee/uni-z-paging",
"types": "global.d.ts",
"engines": {
"HBuilderX": "^3.0.7"
},
@ -48,7 +47,9 @@
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
"app-nvue": "y",
"app-harmony": "u",
"app-uvue": "u"
},
"H5-mobile": {
"Safari": "y",

View File

@ -4,7 +4,7 @@
<img alt="logo" src="https://z-paging.zxlee.cn/img/title-logo.png" height="100" style="margin-bottom: 50px;" />
</p>
[![version](https://img.shields.io/badge/version-2.7.11-blue)](https://github.com/SmileZXLee/uni-z-paging) [![license](https://img.shields.io/github/license/SmileZXLee/uni-z-paging)](https://en.wikipedia.org/wiki/MIT_License)
[![version](https://img.shields.io/badge/version-2.8.3-blue)](https://github.com/SmileZXLee/uni-z-paging) [![license](https://img.shields.io/github/license/SmileZXLee/uni-z-paging)](https://en.wikipedia.org/wiki/MIT_License)
<img height="0" width="0" src="https://api.z-notify.zxlee.cn/v1/public/statistics/8293556910106066944/addOnly?from=uni" />
`z-paging-x`现已支持uniapp x持续完善中插件地址👉🏻 [https://ext.dcloud.net.cn/plugin?name=z-paging-x](https://ext.dcloud.net.cn/plugin?name=z-paging-x)
@ -19,14 +19,15 @@
* 【低耦合低侵入】分页自动管理。在page中无需处理任何分页相关逻辑无需在data中定义任何分页相关变量全由z-paging内部处理。
* 【超灵活支持各种类型自定义】支持自定义下拉刷新自定义上拉加载更多等各种自定义效果支持使用内置自动分页同时也支持通过监听下拉刷新和滚动到底部事件自行处理支持使用自带全屏布局规范同时也支持将z-paging自由放在任意容器中。
* 【功能丰富】支持国际化支持自定义且自动管理空数据图支持主题模式切换支持本地分页支持无闪动聊天分页模式支持展示最后更新时间支持吸顶效果支持内部scroll-view滚动与页面滚动支持一键滚动到顶部支持下拉进入二楼等诸多功能。
* 【全平台兼容】支持vue、nvuevue2、vue3支持h5、app及各家小程序。
* 【高性能】在app-vue、h5、微信小程序、QQ小程序上使用wxs+renderjs视图层实现下拉刷新;支持虚拟列表,轻松渲染万级数据!
* 【【全平台兼容】支持vue&nvuevue2&vue3js&ts支持h5、app、鸿蒙Next及各家小程序。
* 【高性能】在app-vue、h5、微信小程序、QQ小程序上使用wxs+renderjs视图层实现下拉刷新;支持虚拟列表,轻松渲染万级列表数据!
***
### 反馈qq群
* 官方1群`已满`[790460711](https://jq.qq.com/?_wv=1027&k=vU2fKZZH)
* 官方2群[371624008](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=avPmibADf2TNi4LxkIwjCE5vbfXpa-r1&authKey=dQ%2FVDAR87ONxI4b32Py%2BvmXbhnopjHN7%2FJPtdsqJdsCPFZB6zDQ17L06Uh0kITUZ&noverify=0&group_code=371624008)
* 官方2群`已满`[371624008](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=avPmibADf2TNi4LxkIwjCE5vbfXpa-r1&authKey=dQ%2FVDAR87ONxI4b32Py%2BvmXbhnopjHN7%2FJPtdsqJdsCPFZB6zDQ17L06Uh0kITUZ&noverify=0&group_code=371624008)
* 官方3群[343409055](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=sIaNqiCMIjxGQVksjytCw6R8DSiibHR7&authKey=pp995q8ZzFtl5F2xUwvvceP24QTcguWW%2FRVoDnMa8JZF4L2DmS%2B%2FV%2F5sYrcgPsmW&noverify=0&group_code=343409055)
***

View File

@ -0,0 +1,11 @@
declare module 'vue' {
export interface GlobalComponents {
['z-paging']: typeof import('./comps/z-paging')['ZPaging']
['z-paging-swiper']: typeof import('./comps/z-paging-swiper')['ZPagingSwiper']
['z-paging-swiper-item']: typeof import('./comps/z-paging-swiper-item')['ZPagingSwiperItem']
['z-paging-empty-view']: typeof import('./comps/z-paging-empty-view')['ZPagingEmptyView']
['z-paging-cell']: typeof import('./comps/z-paging-cell')['ZPagingCell']
}
}
export {}

View File

@ -0,0 +1,9 @@
export interface AllowedComponentProps {
class?: unknown;
style?: unknown;
}
export interface VNodeProps {
key?: string | number | symbol;
ref?: unknown;
}

View File

@ -0,0 +1,29 @@
import { AllowedComponentProps, VNodeProps } from './_common'
// ****************************** Props ******************************
declare interface ZPagingCellProps {
/**
* z-paging-cell样式
*/
cellStyle?: Record<string, any>
}
// ****************************** Slots ******************************
declare interface ZPagingCellSlots {
// ******************** 主体布局Slot ********************
/**
* view
*/
['default']?: () => any
}
declare interface _ZPagingCell {
new (): {
$props: AllowedComponentProps &
VNodeProps &
ZPagingCellProps
$slots: ZPagingCellSlots
}
}
export declare const ZPagingCell: _ZPagingCell

View File

@ -0,0 +1,95 @@
import { AllowedComponentProps, VNodeProps } from './_common'
// ****************************** Props ******************************
declare interface ZPagingEmptyViewProps {
/**
* z-pagingz-paging的剩余部分
* @default false
* @since 2.0.3
*/
emptyViewFixed?: boolean;
/**
*
* @default "没有数据哦~"
*/
emptyViewText?: string;
/**
* 使z-paging内置的图片
* - 使"@""/"
*/
emptyViewImg?: string;
/**
*
* @default "重新加载"
* @since 1.6.7
*/
emptyViewReloadText?: string;
/**
* view的top等
* - fixed布局`margin-top`
*/
emptyViewStyle?: Record<string, any>;
/**
* img样式
*/
emptyViewImgStyle?: Record<string, any>;
/**
*
*/
emptyViewTitleStyle?: Record<string, any>;
/**
*
* @since 1.6.7
*/
emptyViewReloadStyle?: Record<string, any>;
/**
* ()
* @default false
* @since 1.6.7
*/
showEmptyViewReload?: boolean;
/**
*
* @default false
*/
isLoadFailed?: boolean;
/**
*
* @default 'rpx'
* @since 2.6.7
*/
unit?: 'rpx' | 'px';
// ****************************** Events ******************************
/**
*
*/
onReload?: () => void
/**
* view
* @since 2.3.3
*/
onViewClick?: () => void
}
declare interface _ZPagingEmptyView {
new (): {
$props: AllowedComponentProps &
VNodeProps &
ZPagingEmptyViewProps
}
}
export declare const ZPagingEmptyView: _ZPagingEmptyView

View File

@ -0,0 +1,95 @@
import { AllowedComponentProps, VNodeProps } from './_common'
// ****************************** Props ******************************
declare interface ZPagingSwiperItemProps {
/**
* indexswiper中的第几个
* @default 0
*/
tabIndex?: number
/**
* swiper切换到第几个index
* @default 0
*/
currentIndex?: number
/**
* 使使nvue时nvue中z-paging内置了list组件
* @default false
*/
useVirtualList?: boolean
/**
* cell高度模式`fixed`cell高度完全相同cell高度为准进行计算
* @default 'fixed'
*/
cellHeightMode?: 'fixed' | 'dynamic'
/**
* ()dom越多()
* @default 12
*/
preloadPage?: number | string
/**
* 122
* @default 1
* @since 2.2.8
*/
virtualListCol?: number | string
/**
* scroll取样帧率80
* @default 80
*/
virtualScrollFps?: number | string
/**
* z-paging内部循环渲染列表(使)
* @default false
*/
useInnerList?: boolean
/**
* cell的key名称(nvue有效nvue中开启use-inner-list时必须填此项)
* @since 2.2.7
*/
cellKeyName?: string
/**
* innerList样式
*/
innerListStyle?: Record<string, any>
}
// ****************************** Methods ******************************
declare interface _ZPagingSwiperItemRef {
/**
* pageNo恢复为默认值
*
* @param [animate=false]
*/
reload: (animate?: boolean) => void;
/**
*
* - complete传进去的数组长度小于pageSize时
*
* @param [data]
* @param [success=true]
*/
complete: (data?: any[] | false, success?: boolean) => void;
}
declare interface _ZPagingSwiperItem {
new (): {
$props: AllowedComponentProps &
VNodeProps &
ZPagingSwiperItemProps
}
}
export declare const ZPagingSwiperItem: _ZPagingSwiperItem
export declare const ZPagingSwiperItemRef: _ZPagingSwiperItemRef

View File

@ -0,0 +1,89 @@
import { AllowedComponentProps, VNodeProps } from './_common'
// ****************************** Props ******************************
declare interface ZPagingSwiperProps {
/**
* 使fixed布局使fixed布局z-paging-swiper的父view无需固定高度z-paging高度默认铺满屏幕view请放z-paging-swiper标签内view使用slot="top"view使用slot="bottom"
* @default true
*/
fixed?: boolean
/**
*
* @default false
*/
safeAreaInsetBottom?: boolean
/**
* z-paging-swiper样式
*/
swiperStyle?: Record<string, any>
}
// ****************************** Slots ******************************
declare interface ZPagingSwiperSlots {
// ******************** 主体布局Slot ********************
/**
* view
*/
['default']?: () => any
/**
* tab-view等需要固定的()slot="top"view中
* view时view包住它们view上设置slot="top"需要固定在顶部的view请勿设置position: fixed;
* @since 1.5.5
*/
['top']?: () => any
/**
* ()slot="bottom"view中
* view时view包住它们view上设置slot="bottom"需要固定在底部的view请勿设置position: fixed;
* @since 1.6.2
*/
['bottom']?: () => any
/**
* ()slot="left"view中
* view时view包住它们view上设置slot="left"需要固定在左侧的view请勿设置position: fixed;
* @since 2.2.3
*/
['left']?: () => any
/**
* ()slot="right"view中
* view时view包住它们view上设置slot="right"需要固定在右侧的view请勿设置position: fixed;
* @since 2.2.3
*/
['right']?: () => any
}
// ****************************** Methods ******************************
declare interface _ZPagingSwiperRef {
/**
* slot="left"slot="right"slot="left"slot="right"
*
* @since 2.3.5
*/
updateLeftAndRightWidth: () => void;
/**
* fixed模式下z-paging-swiper的布局onShow时候调用iOS+h5+tabbar+fixed+tabbar页面跳转到无tabbar页面后返回
*
* @since 2.6.5
*/
updateFixedLayout: () => void;
}
declare interface _ZPagingSwiper {
new (): {
$props: AllowedComponentProps &
VNodeProps &
ZPagingSwiperProps
$slots: ZPagingSwiperSlots
}
}
export declare const ZPagingSwiper: _ZPagingSwiper
export declare const ZPagingSwiperRef: _ZPagingSwiperRef

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
/// <reference path="./comps.d.ts" />
declare module 'z-paging' {
export function install() : void
/**
* z-paging全局配置
* - uni.$zp
*
* @since 2.6.5
*/
interface $zp {
/**
*
*/
config : Record<string, any>;
}
global {
interface Uni {
$zp : $zp
}
}
}
declare type ZPagingSwiperRef = typeof import('./comps/z-paging-swiper')['ZPagingSwiperRef']
declare type ZPagingSwiperItemRef = typeof import('./comps/z-paging-swiper-item')['ZPagingSwiperItemRef']