优化快捷回复功能
This commit is contained in:
@@ -83,6 +83,7 @@ type CreateQuickReplyRequest struct {
|
|||||||
Title string `json:"title" binding:"required"`
|
Title string `json:"title" binding:"required"`
|
||||||
Content string `json:"content" binding:"required"`
|
Content string `json:"content" binding:"required"`
|
||||||
SortOrder int `json:"sort_order"`
|
SortOrder int `json:"sort_order"`
|
||||||
|
IsGlobal bool `json:"is_global"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateQuickReplyRequest struct {
|
type UpdateQuickReplyRequest struct {
|
||||||
|
|||||||
@@ -883,7 +883,11 @@ func (r *Repository) ListConversationsWithFilter(principal Principal, page, page
|
|||||||
|
|
||||||
items := make([]ConversationDTO, 0, len(rows))
|
items := make([]ConversationDTO, 0, len(rows))
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
items = append(items, row.toDTO(nil))
|
participants, err := r.participants(row.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, row.toDTO(participants))
|
||||||
}
|
}
|
||||||
return &PaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil
|
return &PaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil
|
||||||
}
|
}
|
||||||
@@ -957,8 +961,12 @@ func (r *Repository) ListQuickReplies(adminID uint64) ([]QuickReplyDTO, error) {
|
|||||||
|
|
||||||
// CreateQuickReply 创建快捷回复
|
// CreateQuickReply 创建快捷回复
|
||||||
func (r *Repository) CreateQuickReply(adminID uint64, req CreateQuickReplyRequest) (*QuickReplyDTO, error) {
|
func (r *Repository) CreateQuickReply(adminID uint64, req CreateQuickReplyRequest) (*QuickReplyDTO, error) {
|
||||||
|
ownerID := adminID
|
||||||
|
if req.IsGlobal {
|
||||||
|
ownerID = 0
|
||||||
|
}
|
||||||
reply := model.ChatQuickReply{
|
reply := model.ChatQuickReply{
|
||||||
AdminUserID: adminID,
|
AdminUserID: ownerID,
|
||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Content: req.Content,
|
Content: req.Content,
|
||||||
SortOrder: req.SortOrder,
|
SortOrder: req.SortOrder,
|
||||||
@@ -972,7 +980,7 @@ func (r *Repository) CreateQuickReply(adminID uint64, req CreateQuickReplyReques
|
|||||||
Title: reply.Title,
|
Title: reply.Title,
|
||||||
Content: reply.Content,
|
Content: reply.Content,
|
||||||
SortOrder: reply.SortOrder,
|
SortOrder: reply.SortOrder,
|
||||||
IsGlobal: false,
|
IsGlobal: reply.AdminUserID == 0,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,6 +161,8 @@ func (s *Service) CreateQuickReply(adminID uint64, req CreateQuickReplyRequest)
|
|||||||
if s.repo == nil {
|
if s.repo == nil {
|
||||||
return nil, ErrDependencyUnavailable
|
return nil, ErrDependencyUnavailable
|
||||||
}
|
}
|
||||||
|
req.Title = strings.TrimSpace(req.Title)
|
||||||
|
req.Content = strings.TrimSpace(req.Content)
|
||||||
if req.Title == "" || req.Content == "" {
|
if req.Title == "" || req.Content == "" {
|
||||||
return nil, ErrInvalidMessage
|
return nil, ErrInvalidMessage
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,11 +156,12 @@ export async function fetchQuickReplies() {
|
|||||||
return data.data
|
return data.data
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createQuickReply(title: string, content: string, sortOrder = 0) {
|
export async function createQuickReply(title: string, content: string, sortOrder = 0, isGlobal = false) {
|
||||||
const { data } = await apiClient.post<ApiResponse<QuickReply>>('/admin/chats/quick-replies', {
|
const { data } = await apiClient.post<ApiResponse<QuickReply>>('/admin/chats/quick-replies', {
|
||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
sort_order: sortOrder,
|
sort_order: sortOrder,
|
||||||
|
is_global: isGlobal,
|
||||||
})
|
})
|
||||||
return data.data
|
return data.data
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,12 +133,16 @@ async function handleLogout() {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.admin-shell {
|
.admin-shell {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: #f5f7fa;
|
background-color: #f5f7fa;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-sidebar {
|
.admin-sidebar {
|
||||||
width: 220px;
|
width: 220px;
|
||||||
|
height: 100vh;
|
||||||
|
flex: none;
|
||||||
background-color: #304156;
|
background-color: #304156;
|
||||||
transition: width 0.3s ease;
|
transition: width 0.3s ease;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -199,8 +203,11 @@ async function handleLogout() {
|
|||||||
|
|
||||||
.admin-menu {
|
.admin-menu {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-menu:not(.el-menu--collapse) {
|
.admin-menu:not(.el-menu--collapse) {
|
||||||
@@ -222,6 +229,9 @@ async function handleLogout() {
|
|||||||
|
|
||||||
.admin-workspace {
|
.admin-workspace {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -229,6 +239,7 @@ async function handleLogout() {
|
|||||||
|
|
||||||
.admin-topbar {
|
.admin-topbar {
|
||||||
height: 56px;
|
height: 56px;
|
||||||
|
flex: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -261,6 +272,7 @@ async function handleLogout() {
|
|||||||
|
|
||||||
.admin-main {
|
.admin-main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -450,8 +450,16 @@ function getSupportName(item: ChatConversation) {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.admin-page {
|
||||||
|
display: grid;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
.page-head {
|
.page-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: none;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
@@ -474,7 +482,7 @@ function getSupportName(item: ChatConversation) {
|
|||||||
|
|
||||||
.chat-workbench {
|
.chat-workbench {
|
||||||
display: grid;
|
display: grid;
|
||||||
min-height: 640px;
|
min-height: 0;
|
||||||
grid-template-columns: 330px minmax(0, 1fr);
|
grid-template-columns: 330px minmax(0, 1fr);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid #e5e7eb;
|
||||||
@@ -483,6 +491,7 @@ function getSupportName(item: ChatConversation) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.conversation-pane {
|
.conversation-pane {
|
||||||
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border-right: 1px solid #e5e7eb;
|
border-right: 1px solid #e5e7eb;
|
||||||
background: #f8fafc;
|
background: #f8fafc;
|
||||||
@@ -567,6 +576,7 @@ function getSupportName(item: ChatConversation) {
|
|||||||
.message-pane {
|
.message-pane {
|
||||||
display: grid;
|
display: grid;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
grid-template-rows: auto minmax(0, 1fr) auto;
|
grid-template-rows: auto minmax(0, 1fr) auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,6 +618,7 @@ function getSupportName(item: ChatConversation) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.message-list {
|
.message-list {
|
||||||
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 18px;
|
padding: 18px;
|
||||||
background: #f3f6fa;
|
background: #f3f6fa;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const form = ref({
|
|||||||
title: '',
|
title: '',
|
||||||
content: '',
|
content: '',
|
||||||
sort_order: 0,
|
sort_order: 0,
|
||||||
|
is_global: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => props.modelValue, (val) => {
|
watch(() => props.modelValue, (val) => {
|
||||||
@@ -51,7 +52,7 @@ async function loadReplies() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resetForm() {
|
function resetForm() {
|
||||||
form.value = { title: '', content: '', sort_order: 0 }
|
form.value = { title: '', content: '', sort_order: 0, is_global: false }
|
||||||
editingId.value = null
|
editingId.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ function startEdit(reply: QuickReply) {
|
|||||||
title: reply.title,
|
title: reply.title,
|
||||||
content: reply.content,
|
content: reply.content,
|
||||||
sort_order: reply.sort_order,
|
sort_order: reply.sort_order,
|
||||||
|
is_global: reply.is_global,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +76,7 @@ async function handleSubmit() {
|
|||||||
await updateQuickReply(editingId.value, form.value)
|
await updateQuickReply(editingId.value, form.value)
|
||||||
ElMessage.success('更新成功')
|
ElMessage.success('更新成功')
|
||||||
} else {
|
} else {
|
||||||
await createQuickReply(form.value.title, form.value.content, form.value.sort_order)
|
await createQuickReply(form.value.title, form.value.content, form.value.sort_order, form.value.is_global)
|
||||||
ElMessage.success('创建成功')
|
ElMessage.success('创建成功')
|
||||||
}
|
}
|
||||||
resetForm()
|
resetForm()
|
||||||
@@ -126,14 +128,23 @@ function handleCancel() {
|
|||||||
style="margin-bottom: 8px"
|
style="margin-bottom: 8px"
|
||||||
/>
|
/>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<el-input-number
|
<div class="form-meta">
|
||||||
v-model="form.sort_order"
|
<label class="sort-field">
|
||||||
:min="0"
|
<span>排序</span>
|
||||||
:max="999"
|
<el-input-number
|
||||||
size="small"
|
v-model="form.sort_order"
|
||||||
placeholder="排序"
|
:min="0"
|
||||||
style="width: 120px"
|
:max="999"
|
||||||
/>
|
size="small"
|
||||||
|
placeholder="排序"
|
||||||
|
style="width: 120px"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<el-radio-group v-model="form.is_global" size="small" :disabled="!!editingId">
|
||||||
|
<el-radio-button :value="false">个人</el-radio-button>
|
||||||
|
<el-radio-button :value="true">全局</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<el-button v-if="editingId" size="small" @click="handleCancel">取消</el-button>
|
<el-button v-if="editingId" size="small" @click="handleCancel">取消</el-button>
|
||||||
<el-button size="small" type="primary" @click="handleSubmit">
|
<el-button size="small" type="primary" @click="handleSubmit">
|
||||||
@@ -196,6 +207,23 @@ function handleCancel() {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-field {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply-list {
|
.reply-list {
|
||||||
|
|||||||
Reference in New Issue
Block a user