Files
medical-mall/pages/mall/admin/kefu/auto_reply.uvue
2026-02-05 09:01:16 +08:00

481 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="admin-main">
<!-- 头部搜索 -->
<view class="search-card">
<view class="search-row">
<view class="search-item">
<text class="search-label">回复类型:</text>
<view class="mock-select">
<text class="select-val">请选择</text>
<text class="arrow-down-icon">▼</text>
</view>
</view>
<view class="search-item">
<text class="search-label">关键字:</text>
<input class="search-input" placeholder="请输入关键字" />
</view>
<button class="btn-query">查询</button>
</view>
</view>
<!-- 数据表格区域 -->
<view class="table-card">
<view class="table-toolbar">
<button class="btn-primary-add" @click="openModal()">添加自动回复</button>
</view>
<view class="table-header-pane">
<view class="th flex-1">ID</view>
<view class="th flex-2">关键字</view>
<view class="th flex-2">回复类型</view>
<view class="th flex-4">回复内容</view>
<view class="th flex-2 text-center">是否开启</view>
<view class="th flex-2 text-center">操作</view>
</view>
<view class="table-body">
<view v-if="list.length === 0" class="empty-box">
<text class="empty-text">暂无数据</text>
</view>
<view v-for="(item, index) in list" :key="index" class="table-row-item">
<text class="td flex-1 color-9">{{ item.id }}</text>
<text class="td flex-2">{{ item.keyword }}</text>
<text class="td flex-2">{{ item.type === 'text' ? '文字消息' : '图片消息' }}</text>
<text class="td flex-4 color-6 truncate">{{ item.content }}</text>
<view class="td flex-2 row-center">
<view class="status-switch-mini" :class="item.status ? 'active' : ''" @click="toggleStatus(index)">
<view class="switch-dot-mini"></view>
</view>
</view>
<view class="td flex-2 row-center">
<text class="btn-action-blue" @click="openModal(item)">编辑</text>
<view class="v-divider-line"></view>
<text class="btn-action-red" @click="deleteItem(index)">删除</text>
</view>
</view>
</view>
</view>
<!-- 添加/编辑弹窗 (Centered Modal) -->
<view class="modal-overlay" v-if="showModal" @click="closeModal">
<view class="modal-main-pane" @click.stop>
<view class="modal-header-box">
<text class="modal-title-txt">客服自动回复</text>
<text class="modal-close-icon" @click="closeModal">×</text>
</view>
<view class="modal-body-form">
<view class="form-item-box">
<view class="label-box"><text class="form-label font-star">关键字:</text></view>
<view class="val-box">
<input class="input-ctrl" v-model="form.keyword" placeholder="请输入关键字" />
</view>
</view>
<view class="form-item-box">
<view class="label-box"><text class="form-label">回复类型:</text></view>
<view class="val-box row-center-start">
<view class="radio-item" @click="form.type = 'text'">
<view class="radio-circle" :class="form.type === 'text' ? 'radio-checked' : ''">
<view v-if="form.type === 'text'" class="radio-dot-inner"></view>
</view>
<text class="radio-txt">文字消息</text>
</view>
<view class="radio-item" @click="form.type = 'image'">
<view class="radio-circle" :class="form.type === 'image' ? 'radio-checked' : ''">
<view v-if="form.type === 'image'" class="radio-dot-inner"></view>
</view>
<text class="radio-txt">图片消息</text>
</view>
</view>
</view>
<view class="form-item-box">
<view class="label-box"><text class="form-label font-star">回复内容:</text></view>
<view class="val-box">
<input class="input-ctrl" v-model="form.content" placeholder="请输入回复内容" />
</view>
</view>
<view class="form-item-box">
<view class="label-box"><text class="form-label">状态:</text></view>
<view class="val-box row-center-start">
<view class="radio-item" @click="form.status = true">
<view class="radio-circle" :class="form.status ? 'radio-checked' : ''">
<view v-if="form.status" class="radio-dot-inner"></view>
</view>
<text class="radio-txt">开启</text>
</view>
<view class="radio-item" @click="form.status = false">
<view class="radio-circle" :class="!form.status ? 'radio-checked' : ''">
<view v-if="!form.status" class="radio-dot-inner"></view>
</view>
<text class="radio-txt">关闭</text>
</view>
</view>
</view>
</view>
<view class="modal-footer-box">
<button class="btn-foot-cancel" @click="closeModal">取消</button>
<button class="btn-foot-submit" @click="saveReply">确定</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive } from 'vue'
interface AutoReplyItem {
id: number;
keyword: string;
type: string; // 'text' | 'image'
content: string;
status: boolean;
}
const list = reactive<AutoReplyItem[]>([])
const showModal = ref(false)
const isEdit = ref(false)
const editIndex = ref(-1)
const form = reactive({
keyword: '',
type: 'text',
content: '',
status: true
})
function openModal(item: AutoReplyItem | null = null) {
if (item != null) {
isEdit.value = true
form.keyword = item.keyword
form.type = item.type
form.content = item.content
form.status = item.status
editIndex.value = list.indexOf(item)
} else {
isEdit.value = false
form.keyword = ''
form.type = 'text'
form.content = ''
form.status = true
}
showModal.value = true
}
function closeModal() {
showModal.value = false
}
function saveReply() {
if (!form.keyword) {
uni.showToast({ title: '请输入关键字', icon: 'none' })
return
}
if (!form.content) {
uni.showToast({ title: '请输入回复内容', icon: 'none' })
return
}
if (isEdit.value) {
const item = list[editIndex.value]
item.keyword = form.keyword
item.type = form.type
item.content = form.content
item.status = form.status
} else {
list.unshift({
id: Date.now() % 10000,
keyword: form.keyword,
type: form.type,
content: form.content,
status: form.status
})
}
closeModal()
uni.showToast({ title: '保存成功', icon: 'success' })
}
function toggleStatus(index: number) {
list[index].status = !list[index].status
}
function deleteItem(index: number) {
uni.showModal({
title: '提示',
content: '确定删除该自动回复吗?',
success: (res) => {
if (res.confirm) {
list.splice(index, 1)
uni.showToast({ title: '删除成功', icon: 'none' })
}
}
})
}
</script>
<style scoped lang="scss">
.admin-main {
padding: 24px;
background-color: #f0f2f5;
min-height: 100vh;
}
/* 搜索栏样式 */
.search-card {
background-color: #fff;
padding: 20px;
border-radius: 4px;
margin-bottom: 20px;
}
.search-row {
display: flex;
flex-direction: row;
align-items: center;
}
.search-item {
display: flex;
flex-direction: row;
align-items: center;
margin-right: 30px;
}
.search-label {
font-size: 14px;
color: #333;
margin-right: 10px;
white-space: nowrap;
}
.mock-select {
width: 200px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.select-val { font-size: 14px; color: #c0c4cc; }
.arrow-down-icon { font-size: 10px; color: #c0c4cc; }
.search-input {
width: 200px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.btn-query {
background-color: #1890ff;
color: #fff;
height: 32px;
line-height: 32px;
padding: 0 20px;
border-radius: 4px;
font-size: 14px;
border: none;
margin-left: 10px;
}
/* 表格区域样式 */
.table-card {
background-color: #fff;
padding: 20px;
border-radius: 4px;
}
.table-toolbar {
margin-bottom: 20px;
}
.btn-primary-add {
background-color: #1890ff;
color: #fff;
height: 32px;
line-height: 32px;
padding: 0 15px;
border-radius: 4px;
font-size: 14px;
border: none;
margin: 0;
}
.table-header-pane {
display: flex;
flex-direction: row;
background-color: #edf1f5;
height: 44px;
align-items: center;
}
.th {
font-size: 14px;
font-weight: bold;
color: #333;
padding: 0 10px;
}
.table-body {
border-bottom: 1px solid #f0f0f0;
}
.table-row-item {
display: flex;
flex-direction: row;
height: 54px;
align-items: center;
border-left: 1px solid #f0f0f0;
border-right: 1px solid #f0f0f0;
border-bottom: 1px solid #f0f0f0;
}
.td {
padding: 0 10px;
font-size: 14px;
color: #606266;
}
.empty-box {
padding: 50px 0;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #f0f0f0;
border-top: none;
}
.empty-text { font-size: 14px; color: #999; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.color-9 { color: #999; }
.color-6 { color: #666; }
/* 操作按钮 */
.btn-action-blue { color: #1890ff; font-size: 14px; cursor: pointer; }
.btn-action-red { color: #ff4d4f; font-size: 14px; cursor: pointer; }
.v-divider-line { width: 1px; height: 12px; background-color: #eee; margin: 0 10px; }
/* 状态开关 */
.status-switch-mini {
width: 44px;
height: 22px;
background-color: #dcdfe6;
border-radius: 11px;
position: relative;
transition: background-color 0.3s;
}
.status-switch-mini.active {
background-color: #1890ff;
}
.switch-dot-mini {
width: 18px;
height: 18px;
background-color: #fff;
border-radius: 50%;
position: absolute;
top: 2px;
left: 2px;
transition: left 0.3s;
}
.status-switch-mini.active .switch-dot-mini {
left: 24px;
}
/* 弹窗样式 */
.modal-overlay {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background-color: rgba(0,0,0,0.5);
z-index: 2000;
display: flex;
justify-content: center;
align-items: center;
}
.modal-main-pane {
width: 600px;
background-color: #fff;
border-radius: 4px;
overflow: hidden;
}
.modal-header-box {
padding: 15px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.modal-title-txt { font-size: 16px; font-weight: 500; color: #333; }
.modal-close-icon { font-size: 22px; color: #999; cursor: pointer; }
.modal-body-form {
padding: 30px;
}
.form-item-box {
display: flex;
flex-direction: row;
margin-bottom: 24px;
align-items: center;
}
.label-box {
width: 100px;
margin-right: 15px;
text-align: right;
}
.form-label { font-size: 14px; color: #606266; }
.font-star::before { content: '*'; color: #ff4d4f; margin-right: 4px; }
.val-box { flex: 1; }
.input-ctrl {
width: 100%;
height: 36px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.radio-item {
display: flex;
flex-direction: row;
align-items: center;
margin-right: 30px;
cursor: pointer;
}
.radio-circle {
width: 16px; height: 16px;
border: 1px solid #dcdfe6;
border-radius: 50%;
margin-right: 8px;
display: flex;
justify-content: center;
align-items: center;
}
.radio-checked { border-color: #1890ff; }
.radio-dot-inner {
width: 8px; height: 8px;
background-color: #1890ff;
border-radius: 50%;
}
.radio-txt { font-size: 14px; color: #606266; }
.modal-footer-box {
padding: 15px 20px;
border-top: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.btn-foot-cancel {
background-color: #fff; border: 1px solid #dcdfe6; color: #606266;
padding: 0 20px; height: 32px; line-height: 32px; border-radius: 4px; font-size: 14px;
margin-right: 15px;
}
.btn-foot-submit {
background-color: #1890ff; color: #fff; border: none;
padding: 0 20px; height: 32px; line-height: 32px; border-radius: 4px; font-size: 14px;
}
/* 布局辅助 */
.flex-1 { flex: 1; }
.flex-2 { flex: 2; }
.flex-4 { flex: 4; }
.row-center { display: flex; flex-direction: row; align-items: center; justify-content: center; }
.row-center-start { display: flex; flex-direction: row; align-items: center; justify-content: flex-start; }
.text-center { text-align: center; }
</style>