254 lines
5.5 KiB
Vue
254 lines
5.5 KiB
Vue
<script setup lang="ts">
|
|
import { ref, watch } from 'vue'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import {
|
|
fetchQuickReplies,
|
|
createQuickReply,
|
|
updateQuickReply,
|
|
deleteQuickReply,
|
|
type QuickReply,
|
|
} from '@/api/chats'
|
|
|
|
const props = defineProps<{
|
|
modelValue: boolean
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: boolean]
|
|
success: []
|
|
}>()
|
|
|
|
const visible = ref(false)
|
|
const loading = ref(false)
|
|
const replies = ref<QuickReply[]>([])
|
|
const editingId = ref<number | null>(null)
|
|
const form = ref({
|
|
title: '',
|
|
content: '',
|
|
sort_order: 0,
|
|
})
|
|
|
|
watch(() => props.modelValue, (val) => {
|
|
visible.value = val
|
|
if (val) {
|
|
loadReplies()
|
|
}
|
|
})
|
|
|
|
watch(visible, (val) => {
|
|
emit('update:modelValue', val)
|
|
})
|
|
|
|
async function loadReplies() {
|
|
loading.value = true
|
|
try {
|
|
replies.value = await fetchQuickReplies()
|
|
} catch {
|
|
ElMessage.error('加载快捷回复失败')
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function resetForm() {
|
|
form.value = { title: '', content: '', sort_order: 0 }
|
|
editingId.value = null
|
|
}
|
|
|
|
function startEdit(reply: QuickReply) {
|
|
editingId.value = reply.id
|
|
form.value = {
|
|
title: reply.title,
|
|
content: reply.content,
|
|
sort_order: reply.sort_order,
|
|
}
|
|
}
|
|
|
|
async function handleSubmit() {
|
|
if (!form.value.title || !form.value.content) {
|
|
ElMessage.warning('标题和内容不能为空')
|
|
return
|
|
}
|
|
try {
|
|
if (editingId.value) {
|
|
await updateQuickReply(editingId.value, form.value)
|
|
ElMessage.success('更新成功')
|
|
} else {
|
|
await createQuickReply(form.value.title, form.value.content, form.value.sort_order)
|
|
ElMessage.success('创建成功')
|
|
}
|
|
resetForm()
|
|
await loadReplies()
|
|
emit('success')
|
|
} catch {
|
|
ElMessage.error('操作失败')
|
|
}
|
|
}
|
|
|
|
async function handleDelete(reply: QuickReply) {
|
|
if (reply.is_global) {
|
|
ElMessage.warning('不能删除全局快捷回复')
|
|
return
|
|
}
|
|
try {
|
|
await ElMessageBox.confirm('确定删除这条快捷回复?', '确认删除', {
|
|
type: 'warning',
|
|
})
|
|
await deleteQuickReply(reply.id)
|
|
ElMessage.success('删除成功')
|
|
await loadReplies()
|
|
emit('success')
|
|
} catch { /* ignore */ }
|
|
}
|
|
|
|
function handleCancel() {
|
|
resetForm()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<el-dialog v-model="visible" title="快捷回复管理" width="600px">
|
|
<div class="quick-reply-content">
|
|
<div class="reply-form">
|
|
<el-input
|
|
v-model="form.title"
|
|
placeholder="快捷回复标题"
|
|
maxlength="64"
|
|
style="margin-bottom: 8px"
|
|
/>
|
|
<el-input
|
|
v-model="form.content"
|
|
type="textarea"
|
|
:rows="3"
|
|
placeholder="回复内容"
|
|
maxlength="500"
|
|
show-word-limit
|
|
style="margin-bottom: 8px"
|
|
/>
|
|
<div class="form-actions">
|
|
<el-input-number
|
|
v-model="form.sort_order"
|
|
:min="0"
|
|
:max="999"
|
|
size="small"
|
|
placeholder="排序"
|
|
style="width: 120px"
|
|
/>
|
|
<div>
|
|
<el-button v-if="editingId" size="small" @click="handleCancel">取消</el-button>
|
|
<el-button size="small" type="primary" @click="handleSubmit">
|
|
{{ editingId ? '更新' : '添加' }}
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="reply-list" v-loading="loading">
|
|
<div
|
|
v-for="reply in replies"
|
|
:key="reply.id"
|
|
class="reply-item"
|
|
:class="{ global: reply.is_global }"
|
|
>
|
|
<div class="reply-info">
|
|
<div class="reply-header">
|
|
<span class="reply-title">{{ reply.title }}</span>
|
|
<el-tag v-if="reply.is_global" size="small" type="info">全局</el-tag>
|
|
<el-tag v-else size="small" type="success">个人</el-tag>
|
|
</div>
|
|
<div class="reply-content">{{ reply.content }}</div>
|
|
</div>
|
|
<div class="reply-actions">
|
|
<el-button link size="small" @click="startEdit(reply)">编辑</el-button>
|
|
<el-button
|
|
v-if="!reply.is_global"
|
|
link
|
|
size="small"
|
|
type="danger"
|
|
@click="handleDelete(reply)"
|
|
>
|
|
删除
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
<el-empty v-if="!loading && replies.length === 0" description="暂无快捷回复" />
|
|
</div>
|
|
</div>
|
|
</el-dialog>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.quick-reply-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
max-height: 60vh;
|
|
}
|
|
|
|
.reply-form {
|
|
padding: 16px;
|
|
background: #f8fafc;
|
|
border-radius: 8px;
|
|
border: 1px solid #e5e7eb;
|
|
}
|
|
|
|
.form-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.reply-list {
|
|
overflow-y: auto;
|
|
max-height: 400px;
|
|
}
|
|
|
|
.reply-item {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
padding: 12px;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 8px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.reply-item.global {
|
|
background: #f0f9ff;
|
|
border-color: #bae6fd;
|
|
}
|
|
|
|
.reply-info {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.reply-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.reply-title {
|
|
font-weight: 500;
|
|
color: #111827;
|
|
}
|
|
|
|
.reply-content {
|
|
color: #6b7280;
|
|
font-size: 13px;
|
|
line-height: 1.4;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.reply-actions {
|
|
display: flex;
|
|
gap: 4px;
|
|
flex-shrink: 0;
|
|
margin-left: 12px;
|
|
}
|
|
</style>
|