提交 1f0c5c61 authored 作者: 王鹏飞's avatar 王鹏飞

chore: update

上级 9e6ace2f
......@@ -20,7 +20,8 @@ import {
QuestionFilled,
EditPen,
DataAnalysis,
ChatDotRound
ChatDotRound,
Setting,
} from '@element-plus/icons-vue'
export const menus: IMenuItem[] = [
{
......@@ -32,37 +33,37 @@ export const menus: IMenuItem[] = [
tag: 'v1-resource-video-list',
icon: VideoCamera,
name: '视频',
path: '/resource/video'
path: '/resource/video',
},
{
tag: 'v1-resource-courseware-list',
icon: Suitcase,
name: '课件',
path: '/resource/courseware'
path: '/resource/courseware',
},
{
tag: 'v1-resource-lesson-plan-list',
icon: FolderOpened,
name: '教案',
path: '/resource/lessonplan'
path: '/resource/lessonplan',
},
{
tag: 'v1-resource-other-information-list',
icon: Files,
name: '其他资料',
path: '/resource/other'
path: '/resource/other',
},
{
icon: Collection,
name: '题库管理',
path: import.meta.env.VITE_QA_CENTER_URL + '/question/list'
path: import.meta.env.VITE_QA_CENTER_URL + '/question/list',
},
{
icon: ToiletPaper,
name: '试卷管理',
path: import.meta.env.VITE_QA_CENTER_URL + '/paper/list'
}
]
path: import.meta.env.VITE_QA_CENTER_URL + '/paper/list',
},
],
},
{
tag: 'v1-course',
......@@ -73,15 +74,15 @@ export const menus: IMenuItem[] = [
tag: 'v1-course-list',
icon: Monitor,
name: '我的课程',
path: '/course/my'
path: '/course/my',
},
{
tag: 'v1-course-create',
icon: Edit,
name: '新建课程',
path: '/course/update-course'
}
]
path: '/course/update-course',
},
],
},
{
tag: 'v1-learning',
......@@ -92,51 +93,51 @@ export const menus: IMenuItem[] = [
tag: 'v1-backend-lecturer-list',
icon: UserFilled,
name: '讲师管理',
path: '/admin/teacher'
path: '/admin/teacher',
},
{
tag: 'v1-learning-teacher-list',
icon: School,
name: '教工用户管理',
path: '/admin/staff'
path: '/admin/staff',
},
{
tag: 'v1-learning-student-list',
icon: User,
name: '学生管理',
path: '/admin/student'
path: '/admin/student',
},
{
tag: 'v1-backend-specialty-list',
icon: Promotion,
name: '专业管理',
path: '/admin/pro'
path: '/admin/pro',
},
{
tag: 'v1-learning-class-list',
icon: School,
name: '班级管理',
path: '/admin/class'
path: '/admin/class',
},
{
tag: 'v1-learning-semester-list',
icon: Guide,
name: '学期管理',
path: '/admin/semester'
path: '/admin/semester',
},
{
tag: 'v1-backend-category-list',
icon: Filter,
name: '类别管理',
path: '/admin/category'
path: '/admin/category',
},
{
icon: Coordinate,
name: '资源审核管理',
path: '/admin/audit'
}
]
path: '/admin/audit',
},
],
},
{
tag: 'v1-backend',
......@@ -147,21 +148,26 @@ export const menus: IMenuItem[] = [
tag: 'v1-backend-data-dictionary-list',
icon: Notebook,
name: '数据字典',
path: '/system/dictionary'
path: '/system/dictionary',
},
{
tag: 'v1-backend-cover-list',
icon: Picture,
name: '封面管理',
path: '/system/cover'
path: '/system/cover',
},
{
tag: 'v1-backend-suggestion-list',
icon: ChatDotRound,
name: ' 投诉建议管理',
path: '/system/suggestion'
}
]
path: '/system/suggestion',
},
{
icon: Setting,
name: '大模型配置',
path: '/system/llm',
},
],
},
{
tag: 'v1-teaching',
......@@ -172,19 +178,19 @@ export const menus: IMenuItem[] = [
tag: 'v1-teaching-discussion',
icon: QuestionFilled,
name: '帖子管理',
path: '/teach/posts'
path: '/teach/posts',
},
{
tag: 'v1-teaching-paper-paper-list',
icon: EditPen,
name: '批改试卷',
path: '/teach/exam'
path: '/teach/exam',
},
{
tag: 'v1-teaching-job-list',
icon: Edit,
name: '批改大作业',
path: '/teach/work'
path: '/teach/work',
},
{
tag: '',
......@@ -192,7 +198,7 @@ export const menus: IMenuItem[] = [
name: '课程资源数据画像',
path:
import.meta.env.VITE_BI_URL +
'/bi/?proc=1&action=viewer&hback=true&isInPreview=true&db=!7d2b!!8346!!6559!!80b2!e-SaaS!2f!!8d44!!6e90!!6570!!636e!!753b!!50cf!.db&platform=PC&browserType=chrome'
'/bi/?proc=1&action=viewer&hback=true&isInPreview=true&db=!7d2b!!8346!!6559!!80b2!e-SaaS!2f!!8d44!!6e90!!6570!!636e!!753b!!50cf!.db&platform=PC&browserType=chrome',
},
{
tag: '',
......@@ -200,7 +206,7 @@ export const menus: IMenuItem[] = [
name: '在线学习数据画像',
path:
import.meta.env.VITE_BI_URL +
'/bi/?proc=1&action=viewer&hback=true&isInPreview=true&db=!7d2b!!8346!!6559!!80b2!e-SaaS!2f!!5b66!!4e60!!884c!!4e3a!!753b!!50cf!.db&platform=PC&browserType=chrome'
'/bi/?proc=1&action=viewer&hback=true&isInPreview=true&db=!7d2b!!8346!!6559!!80b2!e-SaaS!2f!!5b66!!4e60!!884c!!4e3a!!753b!!50cf!.db&platform=PC&browserType=chrome',
},
{
tag: '',
......@@ -208,8 +214,8 @@ export const menus: IMenuItem[] = [
name: '生源地分布',
path:
import.meta.env.VITE_BI_URL +
'/bi/?proc=1&action=viewer&hback=true&isInPreview=true&db=!7d2b!!8346!!6559!!80b2!e-SaaS!2f!!751f!!6e90!!5730!!5206!!5e03!.db&platform=PC&browserType=chrome'
}
]
}
'/bi/?proc=1&action=viewer&hback=true&isInPreview=true&db=!7d2b!!8346!!6559!!80b2!e-SaaS!2f!!751f!!6e90!!5730!!5206!!5e03!.db&platform=PC&browserType=chrome',
},
],
},
]
<template>
<el-dialog
v-model="visible"
:title="isEdit ? '编辑大模型配置' : '新增大模型配置'"
width="800px"
:before-close="handleClose">
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px" class="llm-config-form">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="配置名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入配置名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="提供商" prop="provider">
<el-select v-model="formData.provider" placeholder="请选择提供商" @change="handleProviderChange">
<el-option
v-for="provider in providerOptions"
:key="provider.value"
:label="provider.label"
:value="provider.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="模型名称" prop="model_name">
<el-select v-model="formData.model_name" placeholder="请选择模型" filterable>
<el-option v-for="model in modelOptions" :key="model.value" :label="model.label" :value="model.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="API地址" prop="api_url">
<el-input v-model="formData.api_url" placeholder="请输入API地址" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="API密钥" prop="api_key">
<el-input v-model="formData.api_key" type="password" placeholder="请输入API密钥" show-password />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="formData.description" type="textarea" :rows="3" placeholder="请输入配置描述" />
</el-form-item>
<el-divider content-position="left">高级参数配置</el-divider>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="最大令牌数" prop="max_tokens">
<el-input-number
v-model="formData.max_tokens"
:min="1"
:max="100000"
controls-position="right"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="温度" prop="temperature">
<el-input-number
v-model="formData.temperature"
:min="0"
:max="2"
:step="0.1"
controls-position="right"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="Top P" prop="top_p">
<el-input-number
v-model="formData.top_p"
:min="0"
:max="1"
:step="0.1"
controls-position="right"
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="频率惩罚" prop="frequency_penalty">
<el-input-number
v-model="formData.frequency_penalty"
:min="-2"
:max="2"
:step="0.1"
controls-position="right"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="存在惩罚" prop="presence_penalty">
<el-input-number
v-model="formData.presence_penalty"
:min="-2"
:max="2"
:step="0.1"
controls-position="right"
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="启用状态" prop="is_enabled">
<el-switch v-model="formData.is_enabled" />
<span class="form-tip">启用后该配置将可用于文本处理</span>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitting">
{{ isEdit ? '更新' : '创建' }}
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { llmConfigStorage } from '@/utils/llmStorage'
interface Props {
visible: boolean
editData?: any
}
interface Emits {
(e: 'update:visible', value: boolean): void
(e: 'success'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const formRef = ref<FormInstance>()
const submitting = ref(false)
// 提供商选项
const providerOptions = [
{ label: 'DeepSeek', value: 'deepseek' },
{ label: '通义千问 (Qwen)', value: 'qwen' },
{ label: '自定义', value: 'custom' },
]
// 模型选项(根据提供商动态加载)
const modelOptions = ref<Array<{ label: string; value: string }>>([])
// 表单数据
const formData = reactive({
name: '',
provider: '',
model_name: '',
api_key: '',
api_url: '',
max_tokens: 4000,
temperature: 0.7,
top_p: 0.9,
frequency_penalty: 0,
presence_penalty: 0,
is_enabled: true,
description: '',
})
// 表单验证规则
const rules: FormRules = {
name: [
{ required: true, message: '请输入配置名称', trigger: 'blur' },
{ min: 2, max: 50, message: '配置名称长度在 2 到 50 个字符', trigger: 'blur' },
],
provider: [{ required: true, message: '请选择提供商', trigger: 'change' }],
model_name: [{ required: true, message: '请选择模型名称', trigger: 'change' }],
api_key: [{ required: true, message: '请输入API密钥', trigger: 'blur' }],
api_url: [
{ required: true, message: '请输入API地址', trigger: 'blur' },
{ type: 'url', message: '请输入正确的URL格式', trigger: 'blur' },
],
max_tokens: [{ required: true, message: '请输入最大令牌数', trigger: 'blur' }],
}
// 计算属性
const isEdit = computed(() => !!props.editData)
// 监听对话框显示状态
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.editData) {
Object.assign(formData, props.editData)
} else {
resetForm()
}
loadModelOptions()
}
}
)
// 监听提供商变化
watch(
() => formData.provider,
() => {
loadModelOptions()
formData.model_name = ''
}
)
// 重置表单
const resetForm = () => {
Object.assign(formData, {
name: '',
provider: '',
model_name: '',
api_key: '',
api_url: '',
max_tokens: 4000,
temperature: 0.7,
top_p: 0.9,
frequency_penalty: 0,
presence_penalty: 0,
is_enabled: true,
description: '',
})
formRef.value?.clearValidate()
}
// 加载模型选项
const loadModelOptions = () => {
modelOptions.value = getDefaultModels(formData.provider)
}
// 获取默认模型列表
const getDefaultModels = (provider: string) => {
const modelMap: Record<string, Array<{ label: string; value: string }>> = {
deepseek: [
{ label: 'DeepSeek-V2', value: 'deepseek-chat' },
{ label: 'DeepSeek-Coder', value: 'deepseek-coder' },
],
qwen: [
{ label: 'Qwen2.5-72B-Instruct', value: 'qwen2.5-72b-instruct' },
{ label: 'Qwen2.5-32B-Instruct', value: 'qwen2.5-32b-instruct' },
{ label: 'Qwen2.5-14B-Instruct', value: 'qwen2.5-14b-instruct' },
{ label: 'Qwen2.5-7B-Instruct', value: 'qwen2.5-7b-instruct' },
],
custom: [{ label: '自定义模型', value: 'custom' }],
}
return modelMap[provider] || []
}
// 处理提供商变化
const handleProviderChange = () => {
// 根据提供商设置默认API地址
const defaultUrls: Record<string, string> = {
deepseek: 'https://api.deepseek.com/v1/chat/completions',
qwen: 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation',
custom: '',
}
formData.api_url = defaultUrls[formData.provider] || ''
}
// 移除测试连接功能
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
submitting.value = true
try {
if (isEdit.value && props.editData?.id) {
llmConfigStorage.update(props.editData.id, formData)
ElMessage.success('更新成功')
} else {
llmConfigStorage.create(formData)
ElMessage.success('创建成功')
}
emit('success')
handleClose()
} catch (error: any) {
ElMessage.error(error.message || '操作失败')
} finally {
submitting.value = false
}
}
// 关闭对话框
const handleClose = () => {
emit('update:visible', false)
resetForm()
}
</script>
<style lang="scss" scoped>
.llm-config-form {
.form-tip {
margin-left: 10px;
color: #909399;
font-size: 12px;
}
}
.dialog-footer {
text-align: right;
}
</style>
<template>
<div class="llm-config-list">
<!-- 搜索栏 -->
<div class="search-bar">
<el-row :gutter="20">
<el-col :span="6">
<el-input
v-model="searchForm.name"
placeholder="请输入配置名称"
clearable
@clear="handleSearch"
@keyup.enter="handleSearch">
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</el-col>
<el-col :span="4">
<el-select v-model="searchForm.provider" placeholder="选择提供商" clearable @change="handleSearch">
<el-option
v-for="provider in providerOptions"
:key="provider.value"
:label="provider.label"
:value="provider.value" />
</el-select>
</el-col>
<el-col :span="4">
<el-select v-model="searchForm.is_enabled" placeholder="启用状态" clearable @change="handleSearch">
<el-option label="已启用" value="true" />
<el-option label="已禁用" value="false" />
</el-select>
</el-col>
<el-col :span="6">
<el-button type="primary" @click="handleSearch">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</el-col>
<el-col :span="4" class="text-right">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增配置
</el-button>
</el-col>
</el-row>
</div>
<!-- 表格 -->
<el-table
v-loading="loading"
:data="tableData"
stripe
style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="配置名称" min-width="150" />
<el-table-column prop="provider" label="提供商" width="120">
<template #default="{ row }">
<el-tag :type="getProviderTagType(row.provider) as any">
{{ getProviderLabel(row.provider) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="model_name" label="模型名称" min-width="180" />
<el-table-column prop="api_url" label="API地址" min-width="200" show-overflow-tooltip />
<el-table-column prop="max_tokens" label="最大令牌" width="100" />
<el-table-column prop="temperature" label="温度" width="80" />
<el-table-column prop="is_enabled" label="状态" width="80">
<template #default="{ row }">
<el-switch v-model="row.is_enabled" @change="handleToggleStatus(row)" :loading="row.statusLoading" />
</template>
</el-table-column>
<el-table-column prop="created_time" label="创建时间" width="160">
<template #default="{ row }">
{{ formatDate(row.created_time) }}
</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button type="primary" size="small" @click="handleEdit(row)"> 编辑 </el-button>
<el-button type="danger" size="small" @click="handleDelete(row)"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
<!-- 移除分页组件 -->
<!-- 批量操作 -->
<div v-if="selectedRows.length > 0" class="batch-actions">
<el-alert :title="`已选择 ${selectedRows.length} 项`" type="info" show-icon :closable="false">
<template #default>
<el-button type="danger" size="small" @click="handleBatchDelete"> 批量删除 </el-button>
<el-button type="warning" size="small" @click="handleBatchDisable"> 批量禁用 </el-button>
<el-button type="success" size="small" @click="handleBatchEnable"> 批量启用 </el-button>
</template>
</el-alert>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Refresh, Plus } from '@element-plus/icons-vue'
import { llmConfigStorage } from '@/utils/llmStorage'
interface Props {
onEdit: (data: any) => void
}
const props = defineProps<Props>()
// 搜索表单
const searchForm = reactive({
name: '',
provider: '',
is_enabled: '',
})
// 提供商选项
const providerOptions = [
{ label: 'DeepSeek', value: 'deepseek' },
{ label: '通义千问', value: 'qwen' },
{ label: '自定义', value: 'custom' },
]
// 表格数据
const tableData = ref<any[]>([])
const loading = ref(false)
const selectedRows = ref<any[]>([])
// 移除分页相关变量
// 获取提供商标签类型
const getProviderTagType = (provider: string) => {
const typeMap: Record<string, string> = {
deepseek: 'primary',
qwen: 'success',
custom: 'danger',
}
return typeMap[provider] || 'info'
}
// 获取提供商标签
const getProviderLabel = (provider: string) => {
const option = providerOptions.find((item) => item.value === provider)
return option?.label || provider
}
// 格式化日期
const formatDate = (dateStr?: string) => {
if (!dateStr) return '-'
return new Date(dateStr).toLocaleString('zh-CN')
}
// 加载数据
const loadData = () => {
loading.value = true
try {
// 获取所有数据
let allData = llmConfigStorage.getAll()
// 应用搜索过滤
if (searchForm.name) {
allData = allData.filter((item: any) => item.name.toLowerCase().includes(searchForm.name.toLowerCase()))
}
if (searchForm.provider) {
allData = allData.filter((item: any) => item.provider === searchForm.provider)
}
if (searchForm.is_enabled) {
const enabled = searchForm.is_enabled === 'true'
allData = allData.filter((item: any) => item.is_enabled === enabled)
}
// 直接显示所有数据,不分页
tableData.value = allData
} catch (error: any) {
ElMessage.error(error.message || '加载数据失败')
} finally {
loading.value = false
}
}
// 搜索
const handleSearch = () => {
loadData()
}
// 重置搜索
const handleReset = () => {
Object.assign(searchForm, {
name: '',
provider: '',
is_enabled: '',
})
handleSearch()
}
// 新增
const handleAdd = () => {
props.onEdit({})
}
// 编辑
const handleEdit = (row: any) => {
props.onEdit(row)
}
// 移除测试连接功能
// 删除
const handleDelete = async (row: any) => {
try {
await ElMessageBox.confirm(`确定要删除配置 "${row.name}" 吗?`, '确认删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
if (row.id) {
llmConfigStorage.delete(row.id)
ElMessage.success('删除成功')
loadData()
}
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.message || '删除失败')
}
}
}
// 切换状态
const handleToggleStatus = async (row: any) => {
if (!row.id) return
row.statusLoading = true
try {
llmConfigStorage.update(row.id, { is_enabled: row.is_enabled })
ElMessage.success(row.is_enabled ? '启用成功' : '禁用成功')
} catch (error: any) {
row.is_enabled = !row.is_enabled // 回滚状态
ElMessage.error(error.message || '操作失败')
} finally {
row.statusLoading = false
}
}
// 选择变化
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection
}
// 批量删除
const handleBatchDelete = async () => {
try {
await ElMessageBox.confirm(`确定要删除选中的 ${selectedRows.value.length} 个配置吗?`, '确认批量删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
selectedRows.value.filter((item) => item.id).forEach((item) => llmConfigStorage.delete(item.id))
ElMessage.success('批量删除成功')
loadData()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.message || '批量删除失败')
}
}
}
// 批量禁用
const handleBatchDisable = async () => {
try {
selectedRows.value
.filter((item) => item.id)
.forEach((item) => llmConfigStorage.update(item.id, { is_enabled: false }))
ElMessage.success('批量禁用成功')
loadData()
} catch (error: any) {
ElMessage.error(error.message || '批量禁用失败')
}
}
// 批量启用
const handleBatchEnable = async () => {
try {
selectedRows.value
.filter((item) => item.id)
.forEach((item) => llmConfigStorage.update(item.id, { is_enabled: true }))
ElMessage.success('批量启用成功')
loadData()
} catch (error: any) {
ElMessage.error(error.message || '批量启用失败')
}
}
// 移除分页变化方法
// 暴露刷新方法
defineExpose({
refresh: loadData,
})
onMounted(() => {
loadData()
})
</script>
<style lang="scss" scoped>
.llm-config-list {
.search-bar {
margin-bottom: 20px;
padding: 20px;
background: #f5f7fa;
border-radius: 6px;
}
.pagination-wrapper {
margin-top: 20px;
text-align: right;
}
.batch-actions {
margin-top: 20px;
}
.text-right {
text-align: right;
}
}
</style>
<template>
<div class="sensitive-person-manager">
<!-- 搜索栏 -->
<div class="search-bar">
<el-row :gutter="20">
<el-col :span="6">
<el-input
v-model="searchForm.name"
placeholder="请输入敏感人物姓名"
clearable
@clear="handleSearch"
@keyup.enter="handleSearch">
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</el-col>
<el-col :span="4">
<el-select v-model="searchForm.level" placeholder="敏感级别" clearable @change="handleSearch">
<el-option label="低" value="low" />
<el-option label="中" value="medium" />
<el-option label="高" value="high" />
</el-select>
</el-col>
<el-col :span="6">
<el-button type="primary" @click="handleSearch">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</el-col>
<el-col :span="8" class="text-right">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增敏感人物
</el-button>
</el-col>
</el-row>
</div>
<!-- 表格 -->
<el-table
v-loading="loading"
:data="tableData"
stripe
style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="姓名" min-width="120">
<template #default="{ row }">
<span :class="{ 'sensitive-person': true, [`level-${row.level}`]: true }">
{{ row.name }}
</span>
</template>
</el-table-column>
<el-table-column prop="aliases" label="别名" min-width="200">
<template #default="{ row }">
<el-tag v-for="alias in row.aliases" :key="alias" size="small" class="alias-tag">
{{ alias }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="level" label="敏感级别" width="100">
<template #default="{ row }">
<el-tag :type="getLevelTagType(row.level) as any">
{{ getLevelLabel(row.level) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="is_enabled" label="状态" width="80">
<template #default="{ row }">
<el-switch v-model="row.is_enabled" @change="handleToggleStatus(row)" :loading="row.statusLoading" />
</template>
</el-table-column>
<el-table-column prop="created_time" label="创建时间" width="160">
<template #default="{ row }">
{{ formatDate(row.created_time) }}
</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button type="primary" size="small" @click="handleEdit(row)"> 编辑 </el-button>
<el-button type="danger" size="small" @click="handleDelete(row)"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
<!-- 移除分页组件 -->
<!-- 批量操作 -->
<div v-if="selectedRows.length > 0" class="batch-actions">
<el-alert :title="`已选择 ${selectedRows.length} 项`" type="info" show-icon :closable="false">
<template #default>
<el-button type="danger" size="small" @click="handleBatchDelete"> 批量删除 </el-button>
<el-button type="warning" size="small" @click="handleBatchDisable"> 批量禁用 </el-button>
<el-button type="success" size="small" @click="handleBatchEnable"> 批量启用 </el-button>
</template>
</el-alert>
</div>
<!-- 新增/编辑对话框 -->
<el-dialog v-model="formVisible" :title="isEdit ? '编辑敏感人物' : '新增敏感人物'" width="600px">
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="姓名" prop="name">
<el-input v-model="formData.name" placeholder="请输入敏感人物姓名" />
</el-form-item>
<el-form-item label="别名" prop="aliases">
<el-input
v-model="aliasesText"
type="textarea"
:rows="3"
placeholder="请输入别名,每行一个"
@blur="handleAliasesChange" />
<div class="form-tip">每行输入一个别名,系统会自动识别</div>
</el-form-item>
<el-form-item label="敏感级别" prop="level">
<el-select v-model="formData.level" placeholder="请选择敏感级别">
<el-option label="低" value="low" />
<el-option label="中" value="medium" />
<el-option label="高" value="high" />
</el-select>
</el-form-item>
<el-form-item label="启用状态" prop="is_enabled">
<el-switch v-model="formData.is_enabled" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="formVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitting">
{{ isEdit ? '更新' : '创建' }}
</el-button>
</template>
</el-dialog>
<!-- 移除测试检测对话框 -->
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Refresh, Plus } from '@element-plus/icons-vue'
import type { FormInstance, FormRules } from 'element-plus'
import { sensitivePersonStorage } from '@/utils/llmStorage'
// 搜索表单
const searchForm = reactive({
name: '',
level: '',
})
// 表格数据
const tableData = ref<any[]>([])
const loading = ref(false)
const selectedRows = ref<any[]>([])
// 移除分页相关变量
// 表单相关
const formVisible = ref(false)
const formRef = ref<FormInstance>()
const submitting = ref(false)
const isEdit = ref(false)
const formData = reactive({
name: '',
aliases: [],
level: 'medium',
is_enabled: true,
})
const aliasesText = ref('')
// 移除测试相关变量
// 表单验证规则
const rules: FormRules = {
name: [
{ required: true, message: '请输入敏感人物姓名', trigger: 'blur' },
{ min: 1, max: 50, message: '姓名长度在 1 到 50 个字符', trigger: 'blur' },
],
level: [{ required: true, message: '请选择敏感级别', trigger: 'change' }],
}
// 获取级别标签类型
const getLevelTagType = (level: string) => {
const typeMap: Record<string, string> = {
low: 'info',
medium: 'warning',
high: 'danger',
}
return typeMap[level] || 'info'
}
// 获取级别标签
const getLevelLabel = (level: string) => {
const labelMap: Record<string, string> = {
low: '低',
medium: '中',
high: '高',
}
return labelMap[level] || level
}
// 格式化日期
const formatDate = (dateStr?: string) => {
if (!dateStr) return '-'
return new Date(dateStr).toLocaleString('zh-CN')
}
// 处理别名变化
const handleAliasesChange = () => {
const aliases = aliasesText.value
.split('\n')
.map((alias) => alias.trim())
.filter((alias) => alias)
formData.aliases = aliases as any
}
// 加载数据
const loadData = () => {
loading.value = true
try {
// 获取所有数据
let allData = sensitivePersonStorage.getAll()
// 应用搜索过滤
if (searchForm.name) {
allData = allData.filter(
(item: any) =>
item.name.toLowerCase().includes(searchForm.name.toLowerCase()) ||
item.aliases.some((alias: string) => alias.toLowerCase().includes(searchForm.name.toLowerCase()))
)
}
if (searchForm.level) {
allData = allData.filter((item: any) => item.level === searchForm.level)
}
// 直接显示所有数据,不分页
tableData.value = allData
} catch (error: any) {
ElMessage.error(error.message || '加载数据失败')
} finally {
loading.value = false
}
}
// 搜索
const handleSearch = () => {
loadData()
}
// 重置搜索
const handleReset = () => {
Object.assign(searchForm, {
name: '',
level: '',
})
handleSearch()
}
// 新增
const handleAdd = () => {
isEdit.value = false
Object.assign(formData, {
name: '',
aliases: [],
level: 'medium',
is_enabled: true,
})
aliasesText.value = ''
formVisible.value = true
}
// 编辑
const handleEdit = (row: any) => {
isEdit.value = true
Object.assign(formData, row)
aliasesText.value = row.aliases.join('\n')
formVisible.value = true
}
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
// 更新别名
handleAliasesChange()
submitting.value = true
try {
if (isEdit.value && (formData as any).id) {
sensitivePersonStorage.update((formData as any).id, formData)
ElMessage.success('更新成功')
} else {
sensitivePersonStorage.create(formData)
ElMessage.success('创建成功')
}
formVisible.value = false
loadData()
} catch (error: any) {
ElMessage.error(error.message || '操作失败')
} finally {
submitting.value = false
}
}
// 删除
const handleDelete = async (row: any) => {
try {
await ElMessageBox.confirm(`确定要删除敏感人物 "${row.name}" 吗?`, '确认删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
if (row.id) {
sensitivePersonStorage.delete(row.id)
ElMessage.success('删除成功')
loadData()
}
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.message || '删除失败')
}
}
}
// 切换状态
const handleToggleStatus = async (row: any) => {
if (!row.id) return
row.statusLoading = true
try {
sensitivePersonStorage.update(row.id, { is_enabled: row.is_enabled })
ElMessage.success(row.is_enabled ? '启用成功' : '禁用成功')
} catch (error: any) {
row.is_enabled = !row.is_enabled // 回滚状态
ElMessage.error(error.message || '操作失败')
} finally {
row.statusLoading = false
}
}
// 移除测试检测功能
// 选择变化
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection
}
// 批量删除
const handleBatchDelete = async () => {
try {
await ElMessageBox.confirm(`确定要删除选中的 ${selectedRows.value.length} 个敏感人物吗?`, '确认批量删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
selectedRows.value.filter((item) => item.id).forEach((item) => sensitivePersonStorage.delete(item.id))
ElMessage.success('批量删除成功')
loadData()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.message || '批量删除失败')
}
}
}
// 批量禁用
const handleBatchDisable = async () => {
try {
selectedRows.value
.filter((item) => item.id)
.forEach((item) => sensitivePersonStorage.update(item.id, { is_enabled: false }))
ElMessage.success('批量禁用成功')
loadData()
} catch (error: any) {
ElMessage.error(error.message || '批量禁用失败')
}
}
// 批量启用
const handleBatchEnable = async () => {
try {
selectedRows.value
.filter((item) => item.id)
.forEach((item) => sensitivePersonStorage.update(item.id, { is_enabled: true }))
ElMessage.success('批量启用成功')
loadData()
} catch (error: any) {
ElMessage.error(error.message || '批量启用失败')
}
}
// 移除分页变化方法
onMounted(() => {
loadData()
})
</script>
<style lang="scss" scoped>
.sensitive-person-manager {
.search-bar {
margin-bottom: 20px;
padding: 20px;
background: #f5f7fa;
border-radius: 6px;
}
.pagination-wrapper {
margin-top: 20px;
text-align: right;
}
.batch-actions {
margin-top: 20px;
}
.text-right {
text-align: right;
}
.sensitive-person {
font-weight: bold;
&.level-low {
color: #409eff;
}
&.level-medium {
color: #e6a23c;
}
&.level-high {
color: #f56c6c;
}
}
.alias-tag {
margin-right: 5px;
margin-bottom: 5px;
}
.form-tip {
color: #909399;
font-size: 12px;
margin-top: 5px;
}
.test-result {
margin-top: 20px;
padding: 15px;
background: #f5f7fa;
border-radius: 6px;
.result-item {
display: flex;
align-items: center;
margin-bottom: 10px;
gap: 10px;
.result-name {
font-weight: bold;
color: #f56c6c;
}
.result-alias {
color: #909399;
font-size: 12px;
}
.result-position {
color: #909399;
font-size: 12px;
}
}
}
}
</style>
<template>
<div class="sensitive-word-manager">
<!-- 搜索栏 -->
<div class="search-bar">
<el-row :gutter="20">
<el-col :span="6">
<el-input
v-model="searchForm.word"
placeholder="请输入敏感词"
clearable
@clear="handleSearch"
@keyup.enter="handleSearch">
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</el-col>
<el-col :span="4">
<el-select v-model="searchForm.level" placeholder="敏感级别" clearable @change="handleSearch">
<el-option label="低" value="low" />
<el-option label="中" value="medium" />
<el-option label="高" value="high" />
</el-select>
</el-col>
<el-col :span="6">
<el-button type="primary" @click="handleSearch">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</el-col>
<el-col :span="8" class="text-right">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增敏感词
</el-button>
</el-col>
</el-row>
</div>
<!-- 表格 -->
<el-table
v-loading="loading"
:data="tableData"
stripe
style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="word" label="敏感词" min-width="150">
<template #default="{ row }">
<span :class="{ 'sensitive-word': true, [`level-${row.level}`]: true }">
{{ row.word }}
</span>
</template>
</el-table-column>
<el-table-column prop="level" label="敏感级别" width="100">
<template #default="{ row }">
<el-tag :type="getLevelTagType(row.level) as any">
{{ getLevelLabel(row.level) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="replacement" label="替换词" min-width="150">
<template #default="{ row }">
{{ row.replacement || '***' }}
</template>
</el-table-column>
<el-table-column prop="is_enabled" label="状态" width="80">
<template #default="{ row }">
<el-switch v-model="row.is_enabled" @change="handleToggleStatus(row)" :loading="row.statusLoading" />
</template>
</el-table-column>
<el-table-column prop="created_time" label="创建时间" width="160">
<template #default="{ row }">
{{ formatDate(row.created_time) }}
</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button type="primary" size="small" @click="handleEdit(row)"> 编辑 </el-button>
<el-button type="danger" size="small" @click="handleDelete(row)"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
<!-- 移除分页组件 -->
<!-- 批量操作 -->
<div v-if="selectedRows.length > 0" class="batch-actions">
<el-alert :title="`已选择 ${selectedRows.length} 项`" type="info" show-icon :closable="false">
<template #default>
<el-button type="danger" size="small" @click="handleBatchDelete"> 批量删除 </el-button>
<el-button type="warning" size="small" @click="handleBatchDisable"> 批量禁用 </el-button>
<el-button type="success" size="small" @click="handleBatchEnable"> 批量启用 </el-button>
</template>
</el-alert>
</div>
<!-- 新增/编辑对话框 -->
<el-dialog v-model="formVisible" :title="isEdit ? '编辑敏感词' : '新增敏感词'" width="500px">
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="敏感词" prop="word">
<el-input v-model="formData.word" placeholder="请输入敏感词" />
</el-form-item>
<el-form-item label="敏感级别" prop="level">
<el-select v-model="formData.level" placeholder="请选择敏感级别">
<el-option label="低" value="low" />
<el-option label="中" value="medium" />
<el-option label="高" value="high" />
</el-select>
</el-form-item>
<el-form-item label="替换词" prop="replacement">
<el-input v-model="formData.replacement" placeholder="请输入替换词(可选)" />
</el-form-item>
<el-form-item label="启用状态" prop="is_enabled">
<el-switch v-model="formData.is_enabled" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="formVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitting">
{{ isEdit ? '更新' : '创建' }}
</el-button>
</template>
</el-dialog>
<!-- 移除批量导入和测试检测对话框 -->
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Refresh, Plus } from '@element-plus/icons-vue'
import type { FormInstance, FormRules } from 'element-plus'
import { sensitiveWordStorage } from '@/utils/llmStorage'
// 搜索表单
const searchForm = reactive({
word: '',
level: '',
})
// 表格数据
const tableData = ref<any[]>([])
const loading = ref(false)
const selectedRows = ref<any[]>([])
// 移除分页相关变量
// 表单相关
const formVisible = ref(false)
const formRef = ref<FormInstance>()
const submitting = ref(false)
const isEdit = ref(false)
const formData = reactive({
word: '',
level: 'medium',
replacement: '',
is_enabled: true,
})
// 移除导入和测试相关变量
// 表单验证规则
const rules: FormRules = {
word: [
{ required: true, message: '请输入敏感词', trigger: 'blur' },
{ min: 1, max: 50, message: '敏感词长度在 1 到 50 个字符', trigger: 'blur' },
],
level: [{ required: true, message: '请选择敏感级别', trigger: 'change' }],
}
// 获取级别标签类型
const getLevelTagType = (level: string) => {
const typeMap: Record<string, string> = {
low: 'info',
medium: 'warning',
high: 'danger',
}
return typeMap[level] || 'info'
}
// 获取级别标签
const getLevelLabel = (level: string) => {
const labelMap: Record<string, string> = {
low: '低',
medium: '中',
high: '高',
}
return labelMap[level] || level
}
// 格式化日期
const formatDate = (dateStr?: string) => {
if (!dateStr) return '-'
return new Date(dateStr).toLocaleString('zh-CN')
}
// 加载数据
const loadData = () => {
loading.value = true
try {
// 获取所有数据
let allData = sensitiveWordStorage.getAll()
// 应用搜索过滤
if (searchForm.word) {
allData = allData.filter((item: any) => item.word.toLowerCase().includes(searchForm.word.toLowerCase()))
}
if (searchForm.level) {
allData = allData.filter((item: any) => item.level === searchForm.level)
}
// 直接显示所有数据,不分页
tableData.value = allData
} catch (error: any) {
ElMessage.error(error.message || '加载数据失败')
} finally {
loading.value = false
}
}
// 搜索
const handleSearch = () => {
loadData()
}
// 重置搜索
const handleReset = () => {
Object.assign(searchForm, {
word: '',
level: '',
})
handleSearch()
}
// 新增
const handleAdd = () => {
isEdit.value = false
Object.assign(formData, {
word: '',
level: 'medium',
replacement: '',
is_enabled: true,
})
formVisible.value = true
}
// 编辑
const handleEdit = (row: any) => {
isEdit.value = true
Object.assign(formData, row)
formVisible.value = true
}
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
submitting.value = true
try {
if (isEdit.value && (formData as any).id) {
sensitiveWordStorage.update((formData as any).id, formData)
ElMessage.success('更新成功')
} else {
sensitiveWordStorage.create(formData)
ElMessage.success('创建成功')
}
formVisible.value = false
loadData()
} catch (error: any) {
ElMessage.error(error.message || '操作失败')
} finally {
submitting.value = false
}
}
// 删除
const handleDelete = async (row: any) => {
try {
await ElMessageBox.confirm(`确定要删除敏感词 "${row.word}" 吗?`, '确认删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
if (row.id) {
sensitiveWordStorage.delete(row.id)
ElMessage.success('删除成功')
loadData()
}
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.message || '删除失败')
}
}
}
// 切换状态
const handleToggleStatus = async (row: any) => {
if (!row.id) return
row.statusLoading = true
try {
sensitiveWordStorage.update(row.id, { is_enabled: row.is_enabled })
ElMessage.success(row.is_enabled ? '启用成功' : '禁用成功')
} catch (error: any) {
row.is_enabled = !row.is_enabled // 回滚状态
ElMessage.error(error.message || '操作失败')
} finally {
row.statusLoading = false
}
}
// 移除批量导入和测试检测功能
// 选择变化
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection
}
// 批量删除
const handleBatchDelete = async () => {
try {
await ElMessageBox.confirm(`确定要删除选中的 ${selectedRows.value.length} 个敏感词吗?`, '确认批量删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
selectedRows.value.filter((item) => item.id).forEach((item) => sensitiveWordStorage.delete(item.id))
ElMessage.success('批量删除成功')
loadData()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.message || '批量删除失败')
}
}
}
// 批量禁用
const handleBatchDisable = async () => {
try {
selectedRows.value
.filter((item) => item.id)
.forEach((item) => sensitiveWordStorage.update(item.id, { is_enabled: false }))
ElMessage.success('批量禁用成功')
loadData()
} catch (error: any) {
ElMessage.error(error.message || '批量禁用失败')
}
}
// 批量启用
const handleBatchEnable = async () => {
try {
selectedRows.value
.filter((item) => item.id)
.forEach((item) => sensitiveWordStorage.update(item.id, { is_enabled: true }))
ElMessage.success('批量启用成功')
loadData()
} catch (error: any) {
ElMessage.error(error.message || '批量启用失败')
}
}
// 移除分页变化方法
onMounted(() => {
loadData()
})
</script>
<style lang="scss" scoped>
.sensitive-word-manager {
.search-bar {
margin-bottom: 20px;
padding: 20px;
background: #f5f7fa;
border-radius: 6px;
}
.pagination-wrapper {
margin-top: 20px;
text-align: right;
}
.batch-actions {
margin-top: 20px;
}
.text-right {
text-align: right;
}
.sensitive-word {
font-weight: bold;
&.level-low {
color: #409eff;
}
&.level-medium {
color: #e6a23c;
}
&.level-high {
color: #f56c6c;
}
}
.test-result {
margin-top: 20px;
padding: 15px;
background: #f5f7fa;
border-radius: 6px;
.result-item {
display: flex;
align-items: center;
margin-bottom: 10px;
gap: 10px;
.result-word {
font-weight: bold;
color: #f56c6c;
}
.result-position {
color: #909399;
font-size: 12px;
}
}
}
}
</style>
<template>
<div class="text-process-config">
<el-card class="config-card">
<template #header>
<div class="card-header">
<span>文本处理配置</span>
<el-button type="primary" @click="handleSave" :loading="saving"> 保存配置 </el-button>
</div>
</template>
<el-form ref="formRef" :model="configData" :rules="rules" label-width="200px" class="config-form">
<!-- 意识形态检测配置 -->
<el-divider content-position="left">意识形态检测配置</el-divider>
<el-form-item label="启用意识形态检测" prop="enable_ideology_check">
<el-switch v-model="configData.enable_ideology_check" />
<div class="form-tip">启用后将对文本进行意识形态相关内容的检测和过滤</div>
</el-form-item>
<!-- 敏感词过滤配置 -->
<el-divider content-position="left">敏感词过滤配置</el-divider>
<el-form-item label="启用敏感词过滤" prop="enable_sensitive_word_filter">
<el-switch v-model="configData.enable_sensitive_word_filter" />
<div class="form-tip">启用后将检测并处理文本中的敏感词</div>
</el-form-item>
<el-form-item
v-if="configData.enable_sensitive_word_filter"
label="自动替换敏感词"
prop="auto_replace_sensitive_words">
<el-switch v-model="configData.auto_replace_sensitive_words" />
<div class="form-tip">启用后自动将敏感词替换为指定内容</div>
</el-form-item>
<el-form-item
v-if="configData.enable_sensitive_word_filter && configData.auto_replace_sensitive_words"
label="自定义替换内容"
prop="custom_replacement">
<el-input
v-model="configData.custom_replacement"
placeholder="请输入替换内容,默认为***"
maxlength="50"
show-word-limit />
<div class="form-tip">当检测到敏感词时,将替换为此内容</div>
</el-form-item>
<!-- 敏感人物过滤配置 -->
<el-divider content-position="left">敏感人物过滤配置</el-divider>
<el-form-item label="启用敏感人物过滤" prop="enable_sensitive_person_filter">
<el-switch v-model="configData.enable_sensitive_person_filter" />
<div class="form-tip">启用后将检测并处理文本中涉及的敏感人物</div>
</el-form-item>
</el-form>
</el-card>
<!-- 移除测试区域 -->
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { textProcessStorage } from '@/utils/llmStorage'
// 表单引用
const formRef = ref<FormInstance>()
const saving = ref(false)
// 配置数据
const configData = reactive({
enable_ideology_check: true,
enable_sensitive_word_filter: true,
enable_sensitive_person_filter: true,
auto_replace_sensitive_words: true,
custom_replacement: '***',
})
// 移除测试相关变量
// 表单验证规则
const rules: FormRules = {
custom_replacement: [{ max: 50, message: '替换内容不能超过50个字符', trigger: 'blur' }],
}
// 获取级别标签类型
const getLevelTagType = (level: string) => {
const typeMap: Record<string, string> = {
low: 'info',
medium: 'warning',
high: 'danger',
}
return typeMap[level] || 'info'
}
// 获取级别标签
const getLevelLabel = (level: string) => {
const labelMap: Record<string, string> = {
low: '低',
medium: '中',
high: '高',
}
return labelMap[level] || level
}
// 移除测试相关方法
// 加载配置
const loadConfig = () => {
try {
const config = textProcessStorage.get()
Object.assign(configData, config)
} catch (error: any) {
ElMessage.error(error.message || '加载配置失败')
}
}
// 保存配置
const handleSave = async () => {
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
saving.value = true
try {
textProcessStorage.update(configData)
ElMessage.success('配置保存成功')
} catch (error: any) {
ElMessage.error(error.message || '保存失败')
} finally {
saving.value = false
}
}
// 移除测试文本处理功能
onMounted(() => {
loadConfig()
})
</script>
<style lang="scss" scoped>
.text-process-config {
.config-card,
.test-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.config-form {
.form-tip {
color: #909399;
font-size: 12px;
margin-top: 5px;
}
}
.test-result {
margin-top: 20px;
.result-section {
margin-bottom: 20px;
h4 {
margin-bottom: 10px;
color: #303133;
}
}
.sensitive-words,
.sensitive-persons {
margin-top: 10px;
.word-tag,
.person-tag {
margin-right: 8px;
margin-bottom: 8px;
}
}
.processed-text {
padding: 15px;
background: #f5f7fa;
border-radius: 6px;
border: 1px solid #e4e7ed;
line-height: 1.6;
color: #606266;
}
.score-tip {
margin-top: 10px;
color: #909399;
font-size: 12px;
}
.score-text {
color: #fff;
font-weight: bold;
}
}
}
</style>
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/system/llm',
component: AppLayout,
children: [{ path: '', component: () => import('./views/Index.vue') }]
}
]
<script setup lang="ts">
import { ref } from 'vue'
import { ElTabs, ElTabPane } from 'element-plus'
// 移除类型导入
import LLMConfigList from '../components/LLMConfigList.vue'
import LLMConfigForm from '../components/LLMConfigForm.vue'
import SensitiveWordManager from '../components/SensitiveWordManager.vue'
import SensitivePersonManager from '../components/SensitivePersonManager.vue'
import TextProcessConfig from '../components/TextProcessConfig.vue'
// 当前激活的标签页
const activeTab = ref('llm-config')
// 表单相关
const formVisible = ref(false)
const editData = ref<any>()
// 处理编辑
const handleEdit = (data: any) => {
editData.value = data
formVisible.value = true
}
// 处理表单成功
const handleFormSuccess = () => {
formVisible.value = false
editData.value = undefined
// 刷新列表
if (listRef.value) {
listRef.value.refresh()
}
}
// 列表引用
const listRef = ref()
</script>
<template>
<div class="llm-management">
<el-tabs v-model="activeTab" type="border-card" class="management-tabs">
<!-- 大模型配置 -->
<el-tab-pane label="大模型配置" name="llm-config">
<LLMConfigList :on-edit="handleEdit" ref="listRef" />
</el-tab-pane>
<!-- 敏感词管理 -->
<el-tab-pane label="敏感词管理" name="sensitive-word">
<SensitiveWordManager />
</el-tab-pane>
<!-- 敏感人物管理 -->
<el-tab-pane label="敏感人物管理" name="sensitive-person">
<SensitivePersonManager />
</el-tab-pane>
<!-- 文本处理配置 -->
<el-tab-pane label="文本处理配置" name="text-process">
<TextProcessConfig />
</el-tab-pane>
</el-tabs>
<!-- 大模型配置表单 -->
<LLMConfigForm v-model:visible="formVisible" :edit-data="editData" @success="handleFormSuccess" />
</div>
</template>
<style lang="scss" scoped>
.llm-management {
.management-tabs {
min-height: 600px;
:deep(.el-tabs__content) {
padding: 20px;
}
}
}
</style>
// 本地存储工具函数
// 存储键名常量
const STORAGE_KEYS = {
LLM_CONFIGS: 'llm_configs',
SENSITIVE_WORDS: 'sensitive_words',
SENSITIVE_PERSONS: 'sensitive_persons',
TEXT_PROCESS_CONFIG: 'text_process_config',
LLM_FEATURES: 'llm_features',
}
// 生成唯一ID
const generateId = () => {
return Date.now().toString(36) + Math.random().toString(36).substr(2)
}
// 获取当前时间戳
const getCurrentTime = () => {
return new Date().toISOString()
}
// 通用存储操作
const storage = {
// 获取数据
get<T>(key: string, defaultValue: T): T {
try {
const item = localStorage.getItem(key)
return item ? JSON.parse(item) : defaultValue
} catch (error) {
console.error(`Error getting data from localStorage for key ${key}:`, error)
return defaultValue
}
},
// 设置数据
set<T>(key: string, value: T): void {
try {
localStorage.setItem(key, JSON.stringify(value))
} catch (error) {
console.error(`Error setting data to localStorage for key ${key}:`, error)
}
},
// 删除数据
remove(key: string): void {
try {
localStorage.removeItem(key)
} catch (error) {
console.error(`Error removing data from localStorage for key ${key}:`, error)
}
},
}
// 大模型配置相关操作
export const llmConfigStorage = {
// 获取所有配置
getAll() {
return storage.get(STORAGE_KEYS.LLM_CONFIGS, [] as any[])
},
// 根据ID获取配置
getById(id: string) {
const configs = this.getAll()
return configs.find((config: any) => config.id === id)
},
// 创建配置
create(config: any) {
const configs = this.getAll()
const newConfig = {
...config,
id: generateId(),
created_time: getCurrentTime(),
updated_time: getCurrentTime(),
}
configs.push(newConfig)
storage.set(STORAGE_KEYS.LLM_CONFIGS, configs)
return newConfig
},
// 更新配置
update(id: string, updates: any) {
const configs = this.getAll()
const index = configs.findIndex((config: any) => config.id === id)
if (index !== -1) {
configs[index] = {
...configs[index],
...updates,
updated_time: getCurrentTime(),
}
storage.set(STORAGE_KEYS.LLM_CONFIGS, configs)
return configs[index]
}
return null
},
// 删除配置
delete(id: string) {
const configs = this.getAll()
const filteredConfigs = configs.filter((config: any) => config.id !== id)
storage.set(STORAGE_KEYS.LLM_CONFIGS, filteredConfigs)
return true
},
// 搜索配置
search(params: { name?: string; provider?: string; is_enabled?: string }) {
let configs = this.getAll()
if (params.name) {
configs = configs.filter((config: any) => config.name.toLowerCase().includes(params.name!.toLowerCase()))
}
if (params.provider) {
configs = configs.filter((config: any) => config.provider === params.provider)
}
if (params.is_enabled) {
const enabled = params.is_enabled === 'true'
configs = configs.filter((config: any) => config.is_enabled === enabled)
}
return configs
},
}
// 敏感词相关操作
export const sensitiveWordStorage = {
// 获取所有敏感词
getAll() {
return storage.get(STORAGE_KEYS.SENSITIVE_WORDS, [] as any[])
},
// 根据ID获取敏感词
getById(id: string) {
const words = this.getAll()
return words.find((word: any) => word.id === id)
},
// 创建敏感词
create(word: any) {
const words = this.getAll()
const newWord = {
...word,
id: generateId(),
created_time: getCurrentTime(),
}
words.push(newWord)
storage.set(STORAGE_KEYS.SENSITIVE_WORDS, words)
return newWord
},
// 更新敏感词
update(id: string, updates: any) {
const words = this.getAll()
const index = words.findIndex((word: any) => word.id === id)
if (index !== -1) {
words[index] = { ...words[index], ...updates }
storage.set(STORAGE_KEYS.SENSITIVE_WORDS, words)
return words[index]
}
return null
},
// 删除敏感词
delete(id: string) {
const words = this.getAll()
const filteredWords = words.filter((word: any) => word.id !== id)
storage.set(STORAGE_KEYS.SENSITIVE_WORDS, filteredWords)
return true
},
// 搜索敏感词
search(params: { word?: string; level?: string }) {
let words = this.getAll()
if (params.word) {
words = words.filter((word: any) => word.word.toLowerCase().includes(params.word!.toLowerCase()))
}
if (params.level) {
words = words.filter((word: any) => word.level === params.level)
}
return words
},
// 批量导入
batchImport(words: string[], level: string) {
const existingWords = this.getAll()
const newWords = words.map((word) => ({
id: generateId(),
word: word.trim(),
level,
replacement: '***',
is_enabled: true,
created_time: getCurrentTime(),
}))
const allWords = [...existingWords, ...newWords]
storage.set(STORAGE_KEYS.SENSITIVE_WORDS, allWords)
return newWords
},
}
// 敏感人物相关操作
export const sensitivePersonStorage = {
// 获取所有敏感人物
getAll() {
return storage.get(STORAGE_KEYS.SENSITIVE_PERSONS, [] as any[])
},
// 根据ID获取敏感人物
getById(id: string) {
const persons = this.getAll()
return persons.find((person: any) => person.id === id)
},
// 创建敏感人物
create(person: any) {
const persons = this.getAll()
const newPerson = {
...person,
id: generateId(),
created_time: getCurrentTime(),
}
persons.push(newPerson)
storage.set(STORAGE_KEYS.SENSITIVE_PERSONS, persons)
return newPerson
},
// 更新敏感人物
update(id: string, updates: any) {
const persons = this.getAll()
const index = persons.findIndex((person: any) => person.id === id)
if (index !== -1) {
persons[index] = { ...persons[index], ...updates }
storage.set(STORAGE_KEYS.SENSITIVE_PERSONS, persons)
return persons[index]
}
return null
},
// 删除敏感人物
delete(id: string) {
const persons = this.getAll()
const filteredPersons = persons.filter((person: any) => person.id !== id)
storage.set(STORAGE_KEYS.SENSITIVE_PERSONS, filteredPersons)
return true
},
// 搜索敏感人物
search(params: { name?: string; level?: string }) {
let persons = this.getAll()
if (params.name) {
persons = persons.filter(
(person: any) =>
person.name.toLowerCase().includes(params.name!.toLowerCase()) ||
person.aliases.some((alias: string) => alias.toLowerCase().includes(params.name!.toLowerCase()))
)
}
if (params.level) {
persons = persons.filter((person: any) => person.level === params.level)
}
return persons
},
}
// 文本处理配置相关操作
export const textProcessStorage = {
// 获取配置
get() {
return storage.get(STORAGE_KEYS.TEXT_PROCESS_CONFIG, {
enable_ideology_check: true,
enable_sensitive_word_filter: true,
enable_sensitive_person_filter: true,
auto_replace_sensitive_words: true,
custom_replacement: '***',
})
},
// 更新配置
update(config: any) {
storage.set(STORAGE_KEYS.TEXT_PROCESS_CONFIG, config)
return config
},
}
// 大模型功能配置相关操作
export const llmFeatureStorage = {
// 获取所有功能
getAll() {
return storage.get(STORAGE_KEYS.LLM_FEATURES, [] as any[])
},
// 根据大模型ID获取功能
getByLLMId(llmId: string) {
const features = this.getAll()
return features.filter((feature: any) => feature.llm_id === llmId)
},
// 创建功能
create(feature: any) {
const features = this.getAll()
const newFeature = {
...feature,
id: generateId(),
created_time: getCurrentTime(),
updated_time: getCurrentTime(),
}
features.push(newFeature)
storage.set(STORAGE_KEYS.LLM_FEATURES, features)
return newFeature
},
// 更新功能
update(id: string, updates: any) {
const features = this.getAll()
const index = features.findIndex((feature: any) => feature.id === id)
if (index !== -1) {
features[index] = {
...features[index],
...updates,
updated_time: getCurrentTime(),
}
storage.set(STORAGE_KEYS.LLM_FEATURES, features)
return features[index]
}
return null
},
// 删除功能
delete(id: string) {
const features = this.getAll()
const filteredFeatures = features.filter((feature: any) => feature.id !== id)
storage.set(STORAGE_KEYS.LLM_FEATURES, filteredFeatures)
return true
},
// 获取可用功能模板
getAvailableFeatures() {
return [
{
code: 'text_generation',
name: '文本生成',
description: '基于输入生成文本内容',
},
{
code: 'text_summarization',
name: '文本摘要',
description: '生成文本的摘要内容',
},
{
code: 'text_translation',
name: '文本翻译',
description: '将文本翻译为其他语言',
},
{
code: 'text_analysis',
name: '文本分析',
description: '分析文本的情感、主题等',
},
{
code: 'question_answering',
name: '问答系统',
description: '回答用户提出的问题',
},
]
},
}
// 导出所有存储操作
export default {
llmConfigStorage,
sensitiveWordStorage,
sensitivePersonStorage,
textProcessStorage,
llmFeatureStorage,
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论