Files
medical-mall/pages/mall/admin/kefu/words.uvue
2026-02-03 21:35:57 +08:00

627 lines
14 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-kefu-words">
<view class="words-container">
<!-- 左侧分类 -->
<view class="category-sidebar border-shadow">
<view class="sidebar-header" @click="handleAddCategory">
<text class="plus-icon">+</text>
<text class="header-txt">添加分类</text>
</view>
<scroll-view class="category-list" scroll-y="true">
<view
v-for="cat in categories"
:key="cat.id"
:class="['category-item', activeCategoryId == cat.id ? 'active' : '']"
@click="selectCategory(cat.id)"
>
<view class="folder-icon">📂</view>
<text class="cat-name">{{ cat.name }}</text>
</view>
</scroll-view>
</view>
<!-- 右侧话术列表 -->
<view class="content-main">
<view class="top-bar">
<view class="btn-primary" @click="handleAddWord">
<text class="btn-txt">添加话术</text>
</view>
</view>
<view class="table-card border-shadow">
<view class="table-header">
<view class="th col-id"><text class="th-txt">ID</text></view>
<view class="th col-cat"><text class="th-txt">分类</text></view>
<view class="th col-title"><text class="th-txt">标题</text></view>
<view class="th col-detail"><text class="th-txt">详情</text></view>
<view class="th col-sort"><text class="th-txt">排序</text></view>
<view class="th col-time"><text class="th-txt">添加时间</text></view>
<view class="th col-op"><text class="th-txt">操作</text></view>
</view>
<view class="table-body">
<view class="table-row" v-for="item in wordList" :key="item.id">
<view class="td col-id"><text class="td-txt">{{ item.id }}</text></view>
<view class="td col-cat"><text class="td-txt">{{ item.category }}</text></view>
<view class="td col-title"><text class="td-txt">{{ item.title }}</text></view>
<view class="td col-detail">
<text class="td-txt ellipsis-2">{{ item.content }}</text>
</view>
<view class="td col-sort"><text class="td-txt">{{ item.sort }}</text></view>
<view class="td col-time"><text class="td-txt">{{ item.time }}</text></view>
<view class="td col-op">
<text class="btn-action" @click="handleEdit(item)">编辑</text>
<view class="divider"></view>
<text class="btn-action danger">删除</text>
</view>
</view>
</view>
<!-- 分页 -->
<view class="pagination-bar">
<text class="page-total">共 {{ wordList.length }} 条</text>
<view class="page-size-select">
<text class="size-txt">15条/页</text>
<text class="arrow-down">▼</text>
</view>
<view class="page-nav">
<view class="nav-prev"><text class="nav-icon"> < </text></view>
<view class="nav-item active"><text class="nav-num">1</text></view>
<view class="nav-next"><text class="nav-icon"> > </text></view>
</view>
<view class="page-jump">
<text class="jump-txt">前往</text>
<input class="jump-input" value="1" />
<text class="jump-txt">页</text>
</view>
</view>
</view>
</view>
</view>
<!-- 侧边弹窗 (Drawer) -->
<view v-if="showDrawer" class="drawer-mask" @click="closeDrawer">
<view class="drawer-content" @click.stop>
<view class="drawer-header">
<text class="drawer-title">{{ isEdit ? '编辑话术' : '添加话术' }}</text>
<text class="close-btn" @click="closeDrawer">×</text>
</view>
<view class="drawer-body">
<view class="form-item">
<view class="label-box">
<text class="label-txt">话术分类:</text>
</view>
<view class="input-box">
<view class="select-mock">
<text class="select-val">{{ formData.category || '请选择分类' }}</text>
<text class="arrow-down">▼</text>
</view>
</view>
</view>
<view class="form-item">
<view class="label-box">
<text class="required">*</text>
<text class="label-txt">话术标题:</text>
</view>
<view class="input-box">
<input class="input-base" v-model="formData.title" placeholder="请输入话术标题" />
</view>
</view>
<view class="form-item align-start">
<view class="label-box">
<text class="required">*</text>
<text class="label-txt">话术内容:</text>
</view>
<view class="input-box">
<textarea class="textarea-base" v-model="formData.content" placeholder="请输入话术内容" />
</view>
</view>
<view class="form-item">
<view class="label-box">
<text class="label-txt">排序:</text>
</view>
<view class="input-box">
<input class="input-base" type="number" v-model="formData.sort" />
</view>
</view>
</view>
<view class="drawer-footer">
<view class="btn-cancel" @click="closeDrawer">
<text class="cancel-txt">取消</text>
</view>
<view class="btn-confirm" @click="submitForm">
<text class="confirm-txt">确定</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive } from 'vue'
interface WordItem {
id: string
category: string
title: string
content: string
sort: number
time: string
}
interface Category {
id: number
name: string
}
const categories = ref<Category[]>([
{ id: 1, name: '全部' },
{ id: 2, name: '系统话术' }
])
const activeCategoryId = ref(1)
const wordList = ref<WordItem[]>([
{
id: '67',
category: '系统默认',
title: '旗舰版介绍',
content: '【旗舰版】可以授权给公司或者个人,企业自用搭建,不限制授权域名,提供专属技术总监、产品总监等。',
sort: 0,
time: '2024-09-27 10:15:07'
}
])
// 表单逻辑
const showDrawer = ref(false)
const isEdit = ref(false)
const formData = reactive({
id: '',
category: '系统话术',
title: '',
content: '',
sort: 0
})
const selectCategory = (id: number) => {
activeCategoryId.value = id
}
const handleAddCategory = () => {
console.log('添加分类')
}
const handleAddWord = () => {
isEdit.value = false
formData.id = ''
formData.title = ''
formData.content = ''
formData.sort = 0
showDrawer.value = true
}
const handleEdit = (item: WordItem) => {
isEdit.value = true
formData.id = item.id
formData.category = item.category
formData.title = item.title
formData.content = item.content
formData.sort = item.sort
showDrawer.value = true
}
const closeDrawer = () => {
showDrawer.value = false
}
const submitForm = () => {
console.log('提交表单', formData)
showDrawer.value = false
}
</script>
<style scoped lang="scss">
.admin-kefu-words {
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
}
.border-shadow {
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.words-container {
display: flex;
flex-direction: row;
height: calc(100vh - 40px);
gap: 20px;
}
/* 左侧分类 */
.category-sidebar {
width: 280px;
display: flex;
flex-direction: column;
}
.sidebar-header {
height: 50px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 20px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
}
.plus-icon {
font-size: 18px;
color: #c0c4cc;
margin-right: 8px;
}
.header-txt {
font-size: 14px;
color: #606266;
}
.category-list {
flex: 1;
padding: 10px 0;
}
.category-item {
height: 44px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 20px;
cursor: pointer;
}
.category-item:hover {
background-color: #f5f7fa;
}
.category-item.active {
background-color: #e6f7ff;
border-right: 2px solid #2d8cf0;
}
.folder-icon {
margin-right: 10px;
font-size: 14px;
}
.cat-name {
font-size: 14px;
color: #333;
}
/* 右侧内容 */
.content-main {
flex: 1;
display: flex;
flex-direction: column;
}
.top-bar {
margin-bottom: 20px;
}
.btn-primary {
width: 90px;
height: 34px;
background-color: #2d8cf0;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.btn-txt {
color: #fff;
font-size: 13px;
}
.table-card {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.table-header {
height: 50px;
background-color: #e6f0ff;
display: flex;
flex-direction: row;
align-items: center;
border-bottom: 1px solid #f0f0f0;
}
.th {
display: flex;
align-items: center;
justify-content: center;
padding: 0 10px;
}
.th-txt {
font-size: 13px;
color: #606266;
font-weight: 500;
}
.table-body {
flex: 1;
}
.table-row {
height: 70px;
display: flex;
flex-direction: row;
align-items: center;
border-bottom: 1px solid #f0f0f0;
}
.td {
display: flex;
align-items: center;
justify-content: center;
padding: 0 10px;
}
.td-txt {
font-size: 13px;
color: #606266;
}
.ellipsis-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-align: left;
}
/* 列宽分配 */
.col-id { width: 60px; }
.col-cat { width: 100px; }
.col-title { width: 150px; }
.col-detail { flex: 1; justify-content: flex-start; }
.col-sort { width: 80px; }
.col-time { width: 180px; }
.col-op { width: 150px; }
.btn-action {
font-size: 13px;
color: #2d8cf0;
cursor: pointer;
}
.danger {
color: #ed4014;
}
.divider {
width: 1px;
height: 12px;
background-color: #e8eaec;
margin: 0 10px;
}
/* 分页 */
.pagination-bar {
padding: 15px 20px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
border-top: 1px solid #f0f0f0;
}
.page-total { font-size: 13px; color: #606266; margin-right: 15px; }
.page-size-select {
border: 1px solid #dcdee2;
padding: 4px 10px;
border-radius: 4px;
display: flex;
flex-direction: row;
align-items: center;
margin-right: 15px;
}
.size-txt { font-size: 13px; color: #606266; margin-right: 8px; }
.nav-icon, .arrow-down { font-size: 10px; color: #808695; }
.page-nav { display: flex; flex-direction: row; align-items: center; margin-right: 15px; }
.nav-prev, .nav-next, .nav-item {
width: 30px;
height: 30px;
border: 1px solid #dcdee2;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
margin: 0 4px;
}
.nav-item.active {
background-color: #2d8cf0;
border-color: #2d8cf0;
}
.active .nav-num { color: #fff; }
.nav-num { font-size: 13px; color: #606266; }
.page-jump { display: flex; flex-direction: row; align-items: center; }
.jump-txt { font-size: 13px; color: #606266; }
.jump-input {
width: 40px;
height: 30px;
border: 1px solid #dcdee2;
border-radius: 4px;
text-align: center;
margin: 0 8px;
font-size: 13px;
}
/* 抽屉 Drawer 1:1 复刻 - 修正位置到右侧 */
.drawer-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
z-index: 1000;
}
.drawer-content {
position: absolute;
top: 0;
right: 0; /* 强制靠右占据右边屏幕 */
width: 50%; /* 占屏幕一半宽度 */
height: 100%;
background-color: #fff;
display: flex;
flex-direction: column;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
.drawer-header {
height: 55px;
padding: 0 20px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #f0f0f0;
}
.drawer-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.close-btn {
font-size: 24px;
color: #999;
cursor: pointer;
}
.drawer-body {
flex: 1;
padding: 24px;
}
.form-item {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 24px;
}
.align-start {
align-items: flex-start;
}
.label-box {
width: 100px;
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-right: 15px;
}
.required {
color: #ed4014;
margin-right: 4px;
}
.label-txt {
font-size: 14px;
color: #606266;
}
.input-box {
flex: 1;
}
.select-mock {
height: 36px;
border: 1px solid #dcdee2;
border-radius: 4px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 12px;
}
.select-val { font-size: 14px; color: #333; }
.input-base {
width: 100%;
height: 36px;
border: 1px solid #dcdee2;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.textarea-base {
width: 100%;
height: 120px;
border: 1px solid #dcdee2;
border-radius: 4px;
padding: 10px 12px;
font-size: 14px;
}
.drawer-footer {
height: 60px;
border-top: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
padding: 0 24px;
}
.btn-cancel {
padding: 8px 18px;
border: 1px solid #dcdee2;
border-radius: 4px;
margin-right: 12px;
}
.btn-confirm {
padding: 8px 18px;
background-color: #2d8cf0;
border-radius: 4px;
}
.cancel-txt { font-size: 14px; color: #606266; }
.confirm-txt { font-size: 14px; color: #fff; }
</style>