OA体制内聊天项目人员选择页面新增人名首字母分类,并增加侧边导航栏快速跳转;完全去除群聊相关接口重复注册,便于代码维护

This commit is contained in:
wangyifeng 2025-01-08 16:31:31 +08:00
parent aadc4d9d43
commit 8f519f0efc
9 changed files with 365 additions and 119 deletions

View File

@ -1,29 +0,0 @@
import request from '@/service/index.js'
import qs from 'qs'
// 群公告查询
export const ServeQueryGroupNotice = (data) => {
return request({
url: '/api/v1/group/notice/list',
method: 'GET',
data,
})
}
// 置顶聊天会话
export const ServeTopTalk = (data) => {
return request({
url: '/api/v1/talk/topping',
method: 'POST',
data,
})
}
// 免打扰聊天会话
export const ServeDisturbTalk = (data) => {
return request({
url: '/api/v1/talk/disturb',
method: 'POST',
data,
})
}

View File

@ -1,14 +1,34 @@
<template> <template>
<div class="select-member-item" @click="clickItem(props?.memberItem)"> <div
class="select-member-item"
@click="clickItem(props?.memberItem)"
:class="
props.itemStyle === 'card'
? 'select-member-item-card'
: 'select-member-item-list'
"
>
<div class="member-info"> <div class="member-info">
<slot name="left"></slot> <slot name="left"></slot>
<div class="select-member-item-avatar" v-if="props?.manageType === 'silence'"> <div
class="select-member-item-avatar"
v-if="
props?.manageType === 'silence' ||
props?.manageType === 'searchRecord'
"
>
<img v-if="avatarImg !== 'textImg'" :src="avatarImg" /> <img v-if="avatarImg !== 'textImg'" :src="avatarImg" />
<span v-if="avatarImg === 'textImg'" class="text-[24rpx] font-bold"> <span v-if="avatarImg === 'textImg'" class="text-[24rpx] font-bold">
{{ imgText }} {{ imgText }}
</span> </span>
</div> </div>
<div class="select-member-item-name" v-if="props?.manageType === 'silence'"> <div
class="select-member-item-name"
v-if="
props?.manageType === 'silence' ||
props?.manageType === 'searchRecord'
"
>
<span class="text-[28rpx] font-medium">{{ nameText }}</span> <span class="text-[28rpx] font-medium">{{ nameText }}</span>
</div> </div>
</div> </div>
@ -33,6 +53,7 @@ const emits = defineEmits(['clickItem'])
const props = defineProps({ const props = defineProps({
memberItem: Object, // memberItem: Object, //
manageType: String, // manageType: String, //
itemStyle: String,
}) })
onMounted(() => {}) onMounted(() => {})
@ -69,9 +90,6 @@ const clickItem = () => {
justify-content: space-between; justify-content: space-between;
padding: 18rpx 34rpx; padding: 18rpx 34rpx;
background-color: #fff; background-color: #fff;
margin: 20rpx 0 0;
box-shadow: 0 6px 12px 2px rgba(188, 188, 188, 0.08);
border-radius: 8rpx;
width: 100%; width: 100%;
.member-info { .member-info {
display: flex; display: flex;
@ -112,4 +130,13 @@ const clickItem = () => {
} }
} }
} }
.select-member-item-card {
margin: 20rpx 0 0;
box-shadow: 0 6px 12px 2px rgba(188, 188, 188, 0.08);
border-radius: 8rpx;
}
.select-member-item-list {
border-bottom: 1px solid $theme-border-color;
}
</style> </style>

View File

@ -19,7 +19,7 @@
barIcon="" barIcon=""
color="#46299D" color="#46299D"
unCheckedColor="#EEEEEE" unCheckedColor="#EEEEEE"
:defaultValue="defaultValue" :modelValue="modelValue"
@change="changeSwitch($event, props?.item)" @change="changeSwitch($event, props?.item)"
></tm-switch> ></tm-switch>
</div> </div>
@ -53,7 +53,7 @@ const emits = defineEmits(['toManagePage', 'changeSwitch'])
const toManagePage = (item) => { const toManagePage = (item) => {
emits('toManagePage', item.label) emits('toManagePage', item.label)
} }
const defaultValue = computed(() => { const modelValue = computed(() => {
let switchStatus = false let switchStatus = false
if ( if (
props?.item?.label === t('chat.settings.topSession') && props?.item?.label === t('chat.settings.topSession') &&

View File

@ -43,7 +43,7 @@ const groupStore = useGroupStore()
const dialogueStore = useDialogueStore() const dialogueStore = useDialogueStore()
const dialogueParams = reactive({ const dialogueParams = reactive({
adminList: computed(() => dialogueStore.getSilenceMember), adminList: computed(() => dialogueStore.getAdminList),
}) })
const state = reactive({ const state = reactive({

View File

@ -1,9 +1,23 @@
<template> <template>
<div class="outer-layer select-members-page"> <div class="outer-layer select-members-page">
<div class="root"> <div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false"> <ZPaging
ref="zPaging"
:show-scrollbar="false"
:use-virtual-list="true"
:virtual-list-col="5"
:refresher-enabled="false"
:loading-more-enabled="false"
@scroll="onScroll"
>
<template #top> <template #top>
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220"> <tm-navbar
:hideBack="false"
hideHome
title=""
:leftWidth="220"
id="topArea"
>
<div class="navBar-title flex flex-col items-center justify-center"> <div class="navBar-title flex flex-col items-center justify-center">
<span <span
class="text-[34rpx] font-medium" class="text-[34rpx] font-medium"
@ -17,6 +31,12 @@
> >
{{ $t('chat.manage.addAdmin') }} {{ $t('chat.manage.addAdmin') }}
</span> </span>
<span
class="text-[34rpx] font-medium"
v-if="state.manageType === 'searchRecord'"
>
{{ $t('search.condition.member') }}
</span>
</div> </div>
</tm-navbar> </tm-navbar>
</template> </template>
@ -27,32 +47,90 @@
@inputSearchText="inputSearchText" @inputSearchText="inputSearchText"
></customInput> ></customInput>
</div> </div>
<div class="member-list"> <div
<div class="member-list"
class="member-list-each" :style="{
v-for="(item, index) in dialogueParams?.memberList" padding: state.manageType === 'searchRecord' ? '20rpx 0 0' : '',
:key="index" }"
> >
<tm-checkbox-group v-model="item.checkArr"> <div class="member-list-alphabet-anchor-point">
<selectMemberItem <div
:memberItem="item" class="member-list-alphabet-anchor-point-each"
@clickItem="handleClickItem(item)" v-for="(alphabetItem, alphabetIndex) in state?.alphabet"
:manageType="state.manageType" :key="alphabetIndex"
:style="{
margin: state?.alphabet?.length > 17 ? '0' : '',
}"
@click.stop="scrollToView(alphabetItem)"
>
<span
class="text-[32rpx] font-regular"
:style="{
color:
state.currentAlphabet === alphabetItem ? '#7A58DE' : '',
}"
> >
<template #left> {{ alphabetItem }}
<tm-checkbox </span>
:round="10" </div>
color="#46299d" </div>
:value="item.id" <div
:disabled="item.is_mute === 1" class="member-list-alphabet"
></tm-checkbox> v-for="(alphabetItem, alphabetIndex) in state.resultMemberList"
</template> :key="alphabetIndex"
</selectMemberItem> >
</tm-checkbox-group> <div
class="member-list-alphabet-key"
:style="{
padding:
state.manageType === 'searchRecord' ? '10rpx 30rpx' : '',
}"
v-if="alphabetItem?.memberList?.length > 0"
:id="alphabetItem.key"
:ref="
(el) => {
if (el) alphabetElementRefs[alphabetIndex] = el
}
"
>
<span class="text-[32rpx] font-regular">
{{ alphabetItem.key }}
</span>
</div>
<div v-if="alphabetItem?.memberList?.length > 0">
<div
class="member-list-each"
v-for="(item, index) in alphabetItem?.memberList"
:key="index"
>
<tm-checkbox-group v-model="item.checkArr">
<selectMemberItem
:memberItem="item"
@clickItem="handleClickItem(item)"
:manageType="state.manageType"
:itemStyle="
state.manageType === 'searchRecord' ? 'list' : 'card'
"
>
<template
#left
v-if="state.manageType !== 'searchRecord'"
>
<tm-checkbox
:round="10"
color="#46299d"
:value="item.id"
:disabled="item.is_mute === 1"
></tm-checkbox>
</template>
</selectMemberItem>
</tm-checkbox-group>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<template #bottom> <template #bottom v-if="state.manageType !== 'searchRecord'">
<customBtn <customBtn
:isBottom="true" :isBottom="true"
:btnText="$t('ok')" :btnText="$t('ok')"
@ -68,15 +146,24 @@ import customInput from '@/components/custom-input/custom-input.vue'
import selectMemberItem from '../components/select-member-item.vue' import selectMemberItem from '../components/select-member-item.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue' import customBtn from '@/components/custom-btn/custom-btn.vue'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue' import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { computed, onMounted, reactive } from '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 { onLoad } from '@dcloudio/uni-app'
import { useDialogueStore } from '@/store' import { useDialogueStore } from '@/store'
const zPaging = ref()
useZPaging(zPaging)
const dialogueStore = useDialogueStore() const dialogueStore = useDialogueStore()
const state = reactive({ const state = reactive({
searchText: '', // searchText: '', //
manageType: '', // manageType: '', //
alphabet: [], //A-Z
resultMemberList: [], //A-Z
currentAlphabet: 'A', //A-Z
scrollDirection: '', //
isAssign: false, //view
}) })
const dialogueParams = reactive({ const dialogueParams = reactive({
memberList: computed(() => { memberList: computed(() => {
@ -90,10 +177,24 @@ const dialogueParams = reactive({
receiverId: computed(() => dialogueStore.talk.receiver_id), receiverId: computed(() => dialogueStore.talk.receiver_id),
}) })
watch(
() => dialogueParams?.memberList,
(newMemberList) => {
assembleAlphabetMmeberList(newMemberList)
},
{ deep: true },
)
//A-Z tag
const alphabetElementRefs = ref([])
//
let observer
onLoad((options) => { onLoad((options) => {
console.log(options) console.log(options)
if (options.manageType) { if (options.manageType) {
state.manageType = options.manageType state.manageType = options.manageType
assembleAlphabetMmeberList(dialogueParams?.memberList)
} }
}) })
@ -101,11 +202,64 @@ onMounted(() => {
dialogueParams.memberList.forEach((ele) => { dialogueParams.memberList.forEach((ele) => {
ele.checkArr = [] ele.checkArr = []
}) })
const options = {
root: null, // 使
threshold: 1.0, // 100%
}
observer = new IntersectionObserver(handleIntersection, options)
nextTick(() => {
watch(
alphabetElementRefs,
(newAlphabetElementRefs) => {
if (Array.isArray(newAlphabetElementRefs)) {
newAlphabetElementRefs.forEach((el, index) => {
observeElement(el, index)
})
}
},
{ immediate: true, deep: true },
)
if (alphabetElementRefs.value.length > 0) {
alphabetElementRefs.value.forEach((el, index) =>
observeElement(el, index),
)
}
})
}) })
//
const handleIntersection = (entries) => {
if (state.isAssign) {
state.isAssign = false
return
}
entries.forEach((entry) => {
if (!entry.isIntersecting && state.scrollDirection === 'down') {
state.currentAlphabet = entry.target.id
} else if (entry.isIntersecting && state.scrollDirection === 'up') {
if (state?.alphabet?.length > 1) {
state?.alphabet.forEach((item, index) => {
if (item === entry.target.id && index > 0) {
state.currentAlphabet = state?.alphabet[index - 1]
}
})
} else {
state.currentAlphabet = entry.target.id
}
}
})
}
//
const observeElement = (el, index) => {
if (el && observer) {
observer.observe(el)
}
}
// //
const inputSearchText = (e) => { const inputSearchText = (e) => {
console.log(e) // console.log(e)
state.searchText = e state.searchText = e
} }
@ -140,6 +294,60 @@ const confirmSilenceMember = () => {
console.log(params) console.log(params)
} }
} }
//A-Z
const assembleAlphabetMmeberList = (newMemberList) => {
if (state.manageType === 'searchRecord') {
const resultMemberList = ref([])
const alphabet = Array.from({ length: 26 }, (_, i) =>
String.fromCharCode(i + 65),
)
let tempAlphabet = []
alphabet.forEach((letter) => {
const matchedItems = newMemberList.filter((item) => item.key === letter)
if (matchedItems.length > 0) {
tempAlphabet.push(letter)
}
resultMemberList.value.push({
key: letter,
memberList: matchedItems.length ? matchedItems : [],
})
})
state.alphabet = tempAlphabet
state.resultMemberList = resultMemberList
} else {
state.resultMemberList = [
{
key: '',
memberList: newMemberList,
},
]
}
}
//view
const scrollToView = (alphabet) => {
state.currentAlphabet = alphabet
state.isAssign = true
console.log()
zPaging.value?.scrollIntoViewById(
alphabet,
document.getElementById('topArea').clientHeight
? document.getElementById('topArea').clientHeight - 1
: 80,
)
}
//
const onScroll = (e) => {
if (e.detail.deltaY < 0) {
state.scrollDirection = 'down'
} else if (e.detail.deltaY > 0) {
state.scrollDirection = 'up'
} else {
state.scrollDirection = ''
}
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.outer-layer { .outer-layer {
@ -155,5 +363,39 @@ const confirmSilenceMember = () => {
padding: 22rpx 16rpx; padding: 22rpx 16rpx;
background-color: #fff; background-color: #fff;
} }
.member-list {
.member-list-alphabet-anchor-point {
position: fixed;
right: 32rpx;
top: 0;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.member-list-alphabet-anchor-point-each {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0 0 14rpx;
span {
width: 52rpx;
text-align: center;
line-height: 44rpx;
color: $theme-text;
}
}
}
.member-list-alphabet {
.member-list-alphabet-key {
background-color: #f3f3f3;
span {
line-height: 44rpx;
color: $theme-text;
}
}
}
}
} }
</style> </style>

View File

@ -58,7 +58,7 @@
@toManagePage="toManagePage" @toManagePage="toManagePage"
></settingFormItem> ></settingFormItem>
<groupMemberList <groupMemberList
:memberList="talkParams?.memberList" :memberList="dialogueParams?.memberList"
:memberListsLimit="15" :memberListsLimit="15"
></groupMemberList> ></groupMemberList>
</div> </div>
@ -158,18 +158,14 @@ import {
useGroupStore, useGroupStore,
} from '@/store' } from '@/store'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { import { ServeTopTalkList, ServeSetNotDisturb } from '@/api/chat/index'
ServeQueryGroupNotice,
ServeTopTalk,
ServeDisturbTalk,
} from '@/api/chatSettings/index'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const { t } = useI18n() const { t } = useI18n()
import customInput from '@/components/custom-input/custom-input.vue' import customInput from '@/components/custom-input/custom-input.vue'
const userStore = useUserStore() const userStore = useUserStore()
const talkStore = useTalkStore() const talkStore = useTalkStore()
const dialogueStore = useDialogueStore() const dialogueStore = useDialogueStore()
const talkParams = reactive({ const dialogueParams = reactive({
uid: computed(() => userStore.uid), uid: computed(() => userStore.uid),
index_name: computed(() => dialogueStore.index_name), index_name: computed(() => dialogueStore.index_name),
type: computed(() => dialogueStore.talk.talk_type), type: computed(() => dialogueStore.talk.talk_type),
@ -180,11 +176,14 @@ const talkParams = reactive({
num: computed(() => dialogueStore.members.length), num: computed(() => dialogueStore.members.length),
memberList: computed(() => dialogueStore.members), memberList: computed(() => dialogueStore.members),
}) })
const topItems = computed(() => talkStore.topItems) const talkParams = reactive({
const disturbItems = computed(() => talkStore.disturbItems) topItems: computed(() => talkStore.topItems),
disturbItems: computed(() => talkStore.disturbItems),
})
const groupStore = useGroupStore() const groupStore = useGroupStore()
const groupParams = reactive({ const groupParams = reactive({
groupInfo: computed(() => groupStore.groupInfo), groupInfo: computed(() => groupStore.groupInfo),
groupNotice: computed(() => groupStore.groupNotice),
}) })
const state = reactive({ const state = reactive({
chatGroupMembers: [], //form-item chatGroupMembers: [], //form-item
@ -193,30 +192,29 @@ const state = reactive({
chatSettings: [], // chatSettings: [], //
chatManagement: [], // chatManagement: [], //
groupId: '', //id groupId: '', //id
groupNotice: [], //
sessionId: '', //id sessionId: '', //id
}) })
onLoad(async (options) => { onLoad(async (options) => {
console.log(talkParams) console.log(dialogueParams)
if (options.groupId) { if (options.groupId) {
console.log(options.groupId) console.log(options.groupId)
state.groupId = Number(options.groupId) state.groupId = Number(options.groupId)
await groupStore.setGroupInfo() await groupStore.ServeGroupDetail()
await groupStore.ServeGetGroupNotices()
updateGroupInfos() updateGroupInfos()
getGroupNotice()
} }
if (options.sessionId) { if (options.sessionId) {
state.sessionId = Number(options.sessionId) state.sessionId = Number(options.sessionId)
if (topItems.value.length > 0) { if (talkParams.topItems.length > 0) {
topItems.value.forEach((item) => { talkParams.topItems.forEach((item) => {
if (item.id == options.sessionId) { if (item.id == options.sessionId) {
state.sessionInfo = item state.sessionInfo = item
} }
}) })
} }
if (disturbItems.value.length > 0) { if (talkParams.disturbItems.length > 0) {
disturbItems.value.forEach((item) => { talkParams.disturbItems.forEach((item) => {
if (item.id == options.sessionId) { if (item.id == options.sessionId) {
state.sessionInfo = item state.sessionInfo = item
} }
@ -331,25 +329,6 @@ const groupType = computed(() => {
return groupTypeMapping[groupParams?.groupInfo?.group_type]?.result_type || '' return groupTypeMapping[groupParams?.groupInfo?.group_type]?.result_type || ''
}) })
//
const getGroupNotice = () => {
let params = {
group_id: state.groupId,
}
const resp = ServeQueryGroupNotice(params)
console.log(resp)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
state.groupNotice = data.items
updateGroupInfos()
} else {
}
})
resp.catch(() => {})
}
// //
const updateGroupInfos = () => { const updateGroupInfos = () => {
state.chatGroupMembers = [ state.chatGroupMembers = [
@ -373,7 +352,7 @@ const updateGroupInfos = () => {
label: t('chat.settings.groupNotice'), label: t('chat.settings.groupNotice'),
hasPointer: true, hasPointer: true,
value: '', value: '',
subValue: state.groupNotice[0], subValue: groupParams.groupNotice[0],
customInfo: '', customInfo: '',
}, },
{ {
@ -439,16 +418,23 @@ const toManagePage = (label) => {
// //
const toSearchByConditionPage = (flag) => { const toSearchByConditionPage = (flag) => {
let condition = '' let condition = ''
if (flag == 1) { if (flag == 0) {
condition = 'date' uni.navigateTo({
url:
'/pages/chatSettings/groupManage/selectMembers?manageType=searchRecord',
})
} else {
if (flag == 1) {
condition = 'date'
}
uni.navigateTo({
url:
'/pages/search/searchByCondition/index?condition=' +
condition +
'&receiver_id=' +
state.groupId,
})
} }
uni.navigateTo({
url:
'/pages/search/searchByCondition/index?condition=' +
condition +
'&receiver_id=' +
state.groupId,
})
} }
// //
@ -460,14 +446,14 @@ const changeSwitch = (switchStatus, label) => {
list_id: state.sessionId, //id list_id: state.sessionId, //id
type: switchStatus ? 1 : 2, type: switchStatus ? 1 : 2,
} }
resp = ServeTopTalk(params) resp = ServeTopTalkList(params)
} else if (label == t('chat.settings.messageNoDisturb')) { } else if (label == t('chat.settings.messageNoDisturb')) {
params = { params = {
talk_type: talkParams.type, //12 talk_type: dialogueParams.type, //12
receiver_id: talkParams.receiver_id, //idid receiver_id: dialogueParams.receiver_id, //idid
is_disturb: switchStatus ? 1 : 0, //01 is_disturb: switchStatus ? 1 : 0, //01
} }
resp = ServeDisturbTalk(params) resp = ServeSetNotDisturb(params)
} }
console.log(resp) console.log(resp)
resp.then(({ code, data }) => { resp.then(({ code, data }) => {
@ -475,12 +461,12 @@ const changeSwitch = (switchStatus, label) => {
if (code == 200) { if (code == 200) {
if (label == t('chat.settings.topSession')) { if (label == t('chat.settings.topSession')) {
talkStore.updateItem({ talkStore.updateItem({
index_name: talkParams.index_name, index_name: dialogueParams.index_name,
is_top: switchStatus ? 1 : 2, is_top: switchStatus ? 1 : 2,
}) })
} else if (label == t('chat.settings.messageNoDisturb')) { } else if (label == t('chat.settings.messageNoDisturb')) {
talkStore.updateItem({ talkStore.updateItem({
index_name: talkParams.index_name, index_name: dialogueParams.index_name,
is_disturb: switchStatus ? 1 : 0, is_disturb: switchStatus ? 1 : 0,
}) })
} }

View File

@ -119,6 +119,7 @@ export const useDialogueStore = defineStore('dialogue', {
remark: o.remark, remark: o.remark,
online: false, online: false,
value: o.nickname, value: o.nickname,
key: o.key
})) }))
}, },

View File

@ -1,5 +1,9 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ServeGroupDetail, ServeGetGroupMembers } from '@/api/group/index' import {
ServeGroupDetail,
ServeGetGroupMembers,
ServeGetGroupNotices,
} from '@/api/group/index'
import { useDialogueStore } from '@/store' import { useDialogueStore } from '@/store'
export const useGroupStore = defineStore('group', { export const useGroupStore = defineStore('group', {
@ -7,15 +11,18 @@ export const useGroupStore = defineStore('group', {
return { return {
groupInfo: '', //群聊信息 groupInfo: '', //群聊信息
memberList: [], //群成员列表 memberList: [], //群成员列表
groupNotice: [], //群公告
} }
}, },
getters: { getters: {
//获取群聊信息 //获取群聊信息
getGroupInfo: (state) => state.groupInfo, getGroupInfo: (state) => state.groupInfo,
//获取群公告
getGroupNotice: (state) => state.groupNotice,
}, },
actions: { actions: {
//获取群聊信息 //获取群聊信息
async setGroupInfo() { async ServeGroupDetail() {
const dialogueStore = useDialogueStore() const dialogueStore = useDialogueStore()
let { code, data } = await ServeGroupDetail({ let { code, data } = await ServeGroupDetail({
group_id: dialogueStore.talk.receiver_id, group_id: dialogueStore.talk.receiver_id,
@ -25,5 +32,16 @@ export const useGroupStore = defineStore('group', {
this.groupInfo = data this.groupInfo = data
} }
}, },
//群公告查询
async ServeGetGroupNotices() {
const dialogueStore = useDialogueStore()
let { code, data } = await ServeGetGroupNotices({
group_id: dialogueStore.talk.receiver_id,
})
if (code == 200) {
this.groupNotice = data.items
} else {
}
},
}, },
}) })

View File

@ -115,5 +115,6 @@
"chat.manage.addSilenceMember": "添加禁言成员", "chat.manage.addSilenceMember": "添加禁言成员",
"chatSettings.btn.undoSilence": "解禁", "chatSettings.btn.undoSilence": "解禁",
"silence.tag.hasDone": "已禁言", "silence.tag.hasDone": "已禁言",
"chat.manage.addAdmin": "添加管理员" "chat.manage.addAdmin": "添加管理员",
"search.condition.member": "按群成员查找"
} }