Files
akmon/components/message/MessageInput.uvue
2026-01-20 08:04:15 +08:00

617 lines
15 KiB
Plaintext

<template>
<view class="message-input">
<!-- 消息类型选择 -->
<view class="input-section">
<text class="section-label">消息类型</text>
<picker-view
class="type-picker"
:value="selectedTypeIndex"
@change="handleTypeChange"
>
<picker-view-column>
<view v-for="(type, index) in messageTypes" :key="type.id" class="picker-option">
<text>{{ type.name }}</text>
</view>
</picker-view-column>
</picker-view>
</view>
<!-- 接收者选择 -->
<view class="input-section">
<text class="section-label">发送给</text>
<view class="receiver-selector">
<picker-view
class="receiver-type-picker"
:value="selectedReceiverTypeIndex"
@change="handleReceiverTypeChange"
>
<picker-view-column>
<view v-for="(type, index) in receiverTypes" :key="type.value" class="picker-option">
<text>{{ type.label }}</text>
</view>
</picker-view-column>
</picker-view>
<view v-if="selectedReceiverType === 'user'" class="user-selector">
<input
class="user-input"
placeholder="输入用户名搜索"
v-model="userSearchKeyword"
@input="handleUserSearch"
/>
<scroll-view
v-if="filteredUsers.length > 0"
class="user-list"
scroll-y="true"
>
<view
v-for="user in filteredUsers"
:key="user.id"
class="user-item"
:class="{ 'selected': isUserSelected(user.id) }"
@click="toggleUserSelection(user)"
>
<text class="user-name">{{ user.name }}</text>
<text v-if="isUserSelected(user.id)" class="selected-mark">✓</text>
</view>
</scroll-view>
</view>
<view v-if="selectedReceiverType === 'group'" class="group-selector">
<picker-view
class="group-picker"
:value="selectedGroupIndex"
@change="handleGroupChange"
>
<picker-view-column>
<view v-for="(group, index) in messageGroups" :key="group.id" class="picker-option">
<text>{{ group.name }} ({{ group.member_count }}人)</text>
</view>
</picker-view-column>
</picker-view>
</view>
</view>
</view>
<!-- 消息标题 -->
<view class="input-section">
<text class="section-label">标题</text>
<input
class="title-input"
placeholder="请输入消息标题"
v-model="messageData.title"
:maxlength="100"
/>
</view>
<!-- 消息内容 -->
<view class="input-section">
<text class="section-label">内容 *</text>
<textarea
class="content-textarea"
placeholder="请输入消息内容"
v-model="messageData.content"
:maxlength="2000"
auto-height
/>
<text class="char-count">{{ getContentLength() }}/2000</text>
</view>
<!-- 消息选项 -->
<view class="input-section">
<text class="section-label">消息选项</text>
<view class="options-grid">
<view class="option-item">
<switch
:checked="messageData.is_urgent"
@change="handleUrgentChange"
/>
<text class="option-label">紧急消息</text>
</view>
<view class="option-item">
<switch
:checked="messageData.push_notification"
@change="handlePushChange"
/>
<text class="option-label">推送通知</text>
</view>
<view class="option-item">
<switch
:checked="messageData.email_notification"
@change="handleEmailChange"
/>
<text class="option-label">邮件通知</text>
</view>
<view class="option-item">
<switch
:checked="enableScheduled"
@change="handleScheduledChange"
/>
<text class="option-label">定时发送</text>
</view>
</view>
</view>
<!-- 定时发送设置 -->
<view v-if="enableScheduled" class="input-section">
<text class="section-label">发送时间</text>
<view class="datetime-picker">
<picker-date
:value="scheduledDate"
@change="handleDateChange"
/>
<picker-time
:value="scheduledTime"
@change="handleTimeChange"
/>
</view>
</view>
<!-- 优先级设置 -->
<view class="input-section">
<text class="section-label">优先级</text>
<picker-view
class="priority-picker"
:value="selectedPriorityIndex"
@change="handlePriorityChange"
>
<picker-view-column>
<view v-for="(priority, index) in priorityOptions" :key="priority.value" class="picker-option">
<text>{{ priority.label }}</text>
</view>
</picker-view-column>
</picker-view>
</view>
<!-- 发送按钮 -->
<view class="send-section">
<button
class="send-btn"
:class="{ 'disabled': !canSend() }"
:disabled="!canSend() || sending"
@click="handleSend"
>
<text class="send-text">{{ sending ? '发送中...' : '发送消息' }}</text>
</button>
</view>
</view>
</template>
<script lang="uts">
import type {
MessageType,
SendMessageParams,
UserOption,
GroupOption
} from '../../utils/msgTypes.uts'
type ReceiverTypeOption = {
value: string
label: string
}
type PriorityOption = {
value: number
label: string
}
export default {
name: 'MessageInput',
props: {
messageTypes: {
type: Array as PropType<Array<MessageType>>,
default: (): Array<MessageType> => []
},
availableUsers: {
type: Array as PropType<Array<UserOption>>,
default: (): Array<UserOption> => []
},
messageGroups: {
type: Array as PropType<Array<GroupOption>>,
default: (): Array<GroupOption> => []
},
sending: {
type: Boolean,
default: false
}
},
emits: ['send'],
data() {
return {
selectedTypeIndex: [0] as Array<number>,
selectedReceiverTypeIndex: [0] as Array<number>,
selectedGroupIndex: [0] as Array<number>,
selectedPriorityIndex: [1] as Array<number>,
userSearchKeyword: '' as string,
selectedUsers: [] as Array<UserOption>,
enableScheduled: false as boolean,
scheduledDate: '' as string,
scheduledTime: '' as string,
messageData: {
title: '' as string,
content: '' as string,
is_urgent: false as boolean,
push_notification: true as boolean,
email_notification: false as boolean
},
receiverTypes: [
{ value: 'user', label: '指定用户' },
{ value: 'group', label: '群组' },
{ value: 'broadcast', label: '广播' }
] as Array<ReceiverTypeOption>,
priorityOptions: [
{ value: 0, label: '普通' },
{ value: 50, label: '中等' },
{ value: 80, label: '高' },
{ value: 100, label: '最高' }
] as Array<PriorityOption>
}
},
computed: {
selectedReceiverType(): string {
const index = this.selectedReceiverTypeIndex[0]
return this.receiverTypes[index].value
},
filteredUsers(): Array<UserOption> {
if (this.userSearchKeyword.length === 0) {
return this.availableUsers
}
const keyword = this.userSearchKeyword.toLowerCase()
const filtered: Array<UserOption> = []
for (let i = 0; i < this.availableUsers.length; i++) {
const user = this.availableUsers[i]
if (user.name.toLowerCase().indexOf(keyword) !== -1) {
filtered.push(user)
}
}
return filtered
}
},
methods: {
handleTypeChange(e: UniPickerViewChangeEvent) {
this.selectedTypeIndex = e.detail.value
},
handleReceiverTypeChange(e: UniPickerViewChangeEvent) {
this.selectedReceiverTypeIndex = e.detail.value
// 清空之前的选择
this.selectedUsers = []
this.selectedGroupIndex = [0]
},
handleGroupChange(e: UniPickerViewChangeEvent) {
this.selectedGroupIndex = e.detail.value
},
handlePriorityChange(e: UniPickerViewChangeEvent) {
this.selectedPriorityIndex = e.detail.value
},
handleUserSearch() {
// 搜索逻辑在计算属性中处理
},
isUserSelected(userId: string): boolean {
for (let i = 0; i < this.selectedUsers.length; i++) {
if (this.selectedUsers[i].id === userId) {
return true
}
}
return false
},
toggleUserSelection(user: UserOption) {
const index = this.findUserIndex(user.id)
if (index !== -1) {
this.selectedUsers.splice(index, 1)
} else {
this.selectedUsers.push(user)
}
},
findUserIndex(userId: string): number {
for (let i = 0; i < this.selectedUsers.length; i++) {
if (this.selectedUsers[i].id === userId) {
return i
}
}
return -1
},
handleUrgentChange(e: UniSwitchChangeEvent) {
this.messageData.is_urgent = e.detail.value
},
handlePushChange(e: UniSwitchChangeEvent) {
this.messageData.push_notification = e.detail.value
},
handleEmailChange(e: UniSwitchChangeEvent) {
this.messageData.email_notification = e.detail.value
},
handleScheduledChange(e: UniSwitchChangeEvent) {
this.enableScheduled = e.detail.value
if (!this.enableScheduled) {
this.scheduledDate = ''
this.scheduledTime = ''
}
},
handleDateChange(e: any) {
this.scheduledDate = e.detail.value
},
handleTimeChange(e: any) {
this.scheduledTime = e.detail.value
},
getContentLength(): number {
return this.messageData.content.length
},
canSend(): boolean {
// 必须有内容
if (this.messageData.content.trim().length === 0) {
return false
}
// 必须选择接收者
if (this.selectedReceiverType === 'user' && this.selectedUsers.length === 0) {
return false
}
// 必须选择消息类型
if (this.messageTypes.length === 0) {
return false
}
return true
},
handleSend() {
if (!this.canSend() || this.sending) {
return
}
const typeIndex = this.selectedTypeIndex[0]
const priorityIndex = this.selectedPriorityIndex[0]
const params: SendMessageParams = {
message_type_id: this.messageTypes[typeIndex].id,
receiver_type: this.selectedReceiverType,
receiver_id: this.getReceiverId(),
title: this.messageData.title.length > 0 ? this.messageData.title : null,
content: this.messageData.content.trim(),
content_type: 'text',
attachments: null,
priority: this.priorityOptions[priorityIndex].value,
expires_at: null,
is_urgent: this.messageData.is_urgent,
push_notification: this.messageData.push_notification,
email_notification: this.messageData.email_notification,
sms_notification: false,
scheduled_at: this.getScheduledDateTime(),
conversation_id: null,
parent_message_id: null,
metadata: null
}
this.$emit('send', params)
},
getReceiverId(): string | null {
if (this.selectedReceiverType === 'user') {
if (this.selectedUsers.length === 1) {
return this.selectedUsers[0].id
}
return null // 多用户发送
} else if (this.selectedReceiverType === 'group') {
const groupIndex = this.selectedGroupIndex[0]
return this.messageGroups[groupIndex].id
}
return null
},
getScheduledDateTime(): string | null {
if (!this.enableScheduled || this.scheduledDate.length === 0 || this.scheduledTime.length === 0) {
return null
}
return `${this.scheduledDate} ${this.scheduledTime}:00`
},
resetForm() {
this.messageData.title = ''
this.messageData.content = ''
this.messageData.is_urgent = false
this.messageData.push_notification = true
this.messageData.email_notification = false
this.selectedUsers = []
this.userSearchKeyword = ''
this.enableScheduled = false
this.scheduledDate = ''
this.scheduledTime = ''
this.selectedTypeIndex = [0]
this.selectedReceiverTypeIndex = [0]
this.selectedGroupIndex = [0]
this.selectedPriorityIndex = [1]
}
}
}
</script>
<style scoped>
.message-input {
background-color: #ffffff;
border-radius: 12px;
padding: 16px;
}
.input-section {
margin-bottom: 20px;
}
.section-label {
font-size: 14px;
font-weight: 400;
color: #333333;
margin-bottom: 8px;
display: block;
}
.type-picker,
.receiver-type-picker,
.group-picker,
.priority-picker {
height: 120px;
border: 1px solid #e0e0e0;
border-radius: 6px;
}
.picker-option {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
}
.receiver-selector {
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 12px;
}
.user-selector {
margin-top: 12px;
}
.user-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #e0e0e0;
border-radius: 4px;
font-size: 14px;
}
.user-list {
max-height: 150px;
margin-top: 8px;
border: 1px solid #e0e0e0;
border-radius: 4px;
}
.user-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
border-bottom: 1px solid #f0f0f0;
}
.user-item:last-child {
border-bottom: none;
}
.user-item.selected {
background-color: #e3f2fd;
}
.user-name {
font-size: 14px;
color: #333333;
}
.selected-mark {
font-size: 16px;
color: #2196f3;
}
.title-input {
width: 100%;
padding: 12px;
border: 1px solid #e0e0e0;
border-radius: 6px;
font-size: 16px;
}
.content-textarea {
width: 100%;
min-height: 100px;
padding: 12px;
border: 1px solid #e0e0e0;
border-radius: 6px;
font-size: 14px;
resize: none;
}
.char-count {
font-size: 12px;
color: #999999;
text-align: right;
margin-top: 4px;
}
.options-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.option-item {
display: flex;
align-items: center;
gap: 8px;
}
.option-label {
font-size: 14px;
color: #333333;
}
.datetime-picker {
display: flex;
gap: 12px;
}
.send-section {
margin-top: 24px;
}
.send-btn {
width: 100%;
padding: 16px;
background-color: #2196f3;
border: none;
border-radius: 8px;
}
.send-btn.disabled {
background-color: #cccccc;
}
.send-text {
font-size: 16px;
font-weight: 400;
color: #ffffff;
}
@media (max-width: 480px) {
.options-grid {
grid-template-columns: 1fr;
}
.datetime-picker {
flex-direction: column;
}
}
</style>