617 lines
15 KiB
Plaintext
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>
|