feat: 优化管理后台页面UI、修复TS编译错误、新增人生事件模块
- 优化 AI 配置列表页面:重构统计卡片、搜索表单、表格列展示 - 修复 3 处 TypeScript TS6133 编译错误,恢复构建 - 新增管理员修改密码和重置密码功能 - 优化小程序多个页面样式和交互 - 人生事件模块完善 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -51,3 +51,21 @@ export function deleteAdmin(id: string) {
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
|
||||
// 修改管理员自己的密码(当前登录管理员)
|
||||
export function changeMyPassword(data: { oldPassword: string; newPassword: string }) {
|
||||
return request<ApiResponse<void>>({
|
||||
url: '/admin/auth/changePassword',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 重置指定管理员的密码(超管操作)
|
||||
export function resetAdminPassword(data: { id: string; newPassword: string }) {
|
||||
return request<ApiResponse<void>>({
|
||||
url: '/admin/changePassword',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="profile">个人信息</el-dropdown-item>
|
||||
<el-dropdown-item command="changePassword">修改密码</el-dropdown-item>
|
||||
<el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
@@ -73,6 +74,25 @@
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
|
||||
<!-- 修改密码对话框 -->
|
||||
<el-dialog v-model="passwordDialogVisible" title="修改密码" width="450px">
|
||||
<el-form ref="passwordFormRef" :model="passwordForm" :rules="passwordRules" label-width="80px">
|
||||
<el-form-item label="原密码" prop="oldPassword">
|
||||
<el-input v-model="passwordForm.oldPassword" type="password" show-password placeholder="请输入原密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input v-model="passwordForm.newPassword" type="password" show-password placeholder="请输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input v-model="passwordForm.confirmPassword" type="password" show-password placeholder="请再次输入新密码" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="passwordDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handlePasswordSubmit" :loading="passwordSubmitting">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -81,6 +101,8 @@ import { useRoute } from 'vue-router'
|
||||
import { useAdminStore } from '@/stores/admin'
|
||||
import { Fold, Expand } from '@element-plus/icons-vue'
|
||||
import { menuConfig } from '@/config/menu'
|
||||
import { changeMyPassword } from '@/api/admin'
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
|
||||
const route = useRoute()
|
||||
// const router = useRouter()
|
||||
@@ -91,6 +113,53 @@ const adminInfo = computed(() => adminStore.adminInfo)
|
||||
|
||||
const activeMenu = computed(() => route.path)
|
||||
|
||||
const passwordDialogVisible = ref(false)
|
||||
const passwordSubmitting = ref(false)
|
||||
const passwordFormRef = ref<FormInstance>()
|
||||
const passwordForm = ref({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
})
|
||||
|
||||
const validateConfirmPassword = (_rule: any, value: string, callback: any) => {
|
||||
if (!value) {
|
||||
callback(new Error('请再次输入密码'))
|
||||
} else if (value !== passwordForm.value.newPassword) {
|
||||
callback(new Error('两次输入的密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const passwordRules: FormRules = {
|
||||
oldPassword: [{ required: true, message: '请输入原密码', trigger: 'blur' }],
|
||||
newPassword: [{ required: true, message: '请输入新密码', trigger: 'blur' }],
|
||||
confirmPassword: [{ required: true, validator: validateConfirmPassword, trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const handlePasswordSubmit = async () => {
|
||||
if (!passwordFormRef.value) return
|
||||
await passwordFormRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
passwordSubmitting.value = true
|
||||
try {
|
||||
await changeMyPassword({
|
||||
oldPassword: passwordForm.value.oldPassword,
|
||||
newPassword: passwordForm.value.newPassword
|
||||
})
|
||||
ElMessage.success('密码修改成功,请重新登录')
|
||||
passwordDialogVisible.value = false
|
||||
passwordForm.value = { oldPassword: '', newPassword: '', confirmPassword: '' }
|
||||
adminStore.logout()
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.response?.data?.message || '修改密码失败')
|
||||
} finally {
|
||||
passwordSubmitting.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const menuRoutes = computed(() => {
|
||||
return menuConfig.filter(item => !item.hidden)
|
||||
})
|
||||
@@ -104,6 +173,8 @@ const handleCommand = (command: string) => {
|
||||
adminStore.logout()
|
||||
} else if (command === 'profile') {
|
||||
// TODO: 跳转到个人信息页面
|
||||
} else if (command === 'changePassword') {
|
||||
passwordDialogVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="warning" link @click="handleChangePassword(row)">修改密码</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -128,13 +129,32 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 修改密码对话框 -->
|
||||
<el-dialog v-model="changePasswordVisible" title="修改密码" width="450px">
|
||||
<el-form ref="changePwFormRef" :model="changePwForm" :rules="changePwRules" label-width="80px">
|
||||
<el-form-item label="账号">
|
||||
<el-input :model-value="changePwForm.account" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input v-model="changePwForm.newPassword" type="password" show-password placeholder="请输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input v-model="changePwForm.confirmPassword" type="password" show-password placeholder="请再次输入新密码" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="changePasswordVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleResetPasswordSubmit" :loading="changePwSubmitting">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { getAdminPage, createAdmin, updateAdmin, deleteAdmin } from '@/api/admin'
|
||||
import { getAdminPage, createAdmin, updateAdmin, deleteAdmin, resetAdminPassword } from '@/api/admin'
|
||||
import type { Admin, AdminPageRequest } from '@/types/admin'
|
||||
import { validateAccount, validatePassword, validateEmail, validatePhone } from '@/utils/validate'
|
||||
|
||||
@@ -181,6 +201,31 @@ const formRules: FormRules = {
|
||||
|
||||
const dialogTitle = ref('新增管理员')
|
||||
|
||||
const changePasswordVisible = ref(false)
|
||||
const changePwSubmitting = ref(false)
|
||||
const changePwFormRef = ref<FormInstance>()
|
||||
const changePwForm = reactive({
|
||||
id: '',
|
||||
account: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
})
|
||||
|
||||
const validateConfirmPw = (_rule: any, value: string, callback: any) => {
|
||||
if (!value) {
|
||||
callback(new Error('请再次输入密码'))
|
||||
} else if (value !== changePwForm.newPassword) {
|
||||
callback(new Error('两次输入的密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const changePwRules: FormRules = {
|
||||
newPassword: [{ required: true, message: '请输入新密码', trigger: 'blur' }],
|
||||
confirmPassword: [{ required: true, validator: validateConfirmPw, trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const fetchData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -245,6 +290,34 @@ const handleDelete = (row: Admin) => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleChangePassword = (row: Admin) => {
|
||||
changePwForm.id = row.id
|
||||
changePwForm.account = row.account
|
||||
changePwForm.newPassword = ''
|
||||
changePwForm.confirmPassword = ''
|
||||
changePasswordVisible.value = true
|
||||
}
|
||||
|
||||
const handleResetPasswordSubmit = async () => {
|
||||
if (!changePwFormRef.value) return
|
||||
await changePwFormRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
changePwSubmitting.value = true
|
||||
try {
|
||||
await resetAdminPassword({
|
||||
id: changePwForm.id,
|
||||
newPassword: changePwForm.newPassword
|
||||
})
|
||||
ElMessage.success('密码重置成功')
|
||||
changePasswordVisible.value = false
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.response?.data?.message || '重置密码失败')
|
||||
} finally {
|
||||
changePwSubmitting.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
|
||||
@@ -1,183 +1,202 @@
|
||||
<template>
|
||||
<div class="ai-config-list">
|
||||
<h2 class="page-title">AI配置管理</h2>
|
||||
|
||||
<el-card class="search-card">
|
||||
<div class="page-header">
|
||||
<div class="header-left">
|
||||
<h2 class="page-title">AI 配置管理</h2>
|
||||
<p class="page-desc">管理 AI 服务提供商的 API 配置、参数、费用及使用场景</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<el-button type="primary" @click="handleAdd" size="large">
|
||||
<el-icon><Plus /></el-icon>
|
||||
新增配置
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索过滤 -->
|
||||
<el-card class="search-card" shadow="never">
|
||||
<el-form :model="searchForm" :inline="true" class="search-form">
|
||||
<el-form-item label="关键词">
|
||||
<el-input v-model="searchForm.keyword" placeholder="配置名称/键值/描述" clearable style="width: 200px" />
|
||||
<el-input v-model="searchForm.keyword" placeholder="配置名称/键值/描述" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="配置类型">
|
||||
<el-select v-model="searchForm.configType" placeholder="请选择配置类型" clearable style="width: 150px">
|
||||
<el-option
|
||||
v-for="item in CONFIG_TYPE_OPTIONS"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
<el-select v-model="searchForm.configType" placeholder="全部" clearable>
|
||||
<el-option
|
||||
v-for="item in CONFIG_TYPE_OPTIONS"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="服务提供商">
|
||||
<el-select v-model="searchForm.provider" placeholder="请选择服务提供商" clearable style="width: 150px">
|
||||
<el-option
|
||||
v-for="item in PROVIDER_OPTIONS"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
<el-select v-model="searchForm.provider" placeholder="全部" clearable>
|
||||
<el-option
|
||||
v-for="item in PROVIDER_OPTIONS"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="使用场景">
|
||||
<el-select v-model="searchForm.usageScenario" placeholder="请选择使用场景" clearable style="width: 150px">
|
||||
<el-option
|
||||
v-for="item in USAGE_SCENARIO_OPTIONS"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
<el-select v-model="searchForm.usageScenario" placeholder="全部" clearable>
|
||||
<el-option
|
||||
v-for="item in USAGE_SCENARIO_OPTIONS"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.isEnabled" placeholder="请选择状态" clearable style="width: 120px">
|
||||
<el-select v-model="searchForm.isEnabled" placeholder="全部" clearable>
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="禁用" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="环境">
|
||||
<el-select v-model="searchForm.environment" placeholder="请选择环境" clearable style="width: 130px">
|
||||
<el-option
|
||||
v-for="item in ENVIRONMENT_OPTIONS"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-form-item class="search-actions">
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card class="table-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>AI配置列表</span>
|
||||
<div class="header-actions">
|
||||
<el-button type="success" @click="handleRefreshStats">刷新统计</el-button>
|
||||
<el-button type="primary" @click="handleAdd">新增配置</el-button>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" class="stats-row">
|
||||
<el-col :span="6">
|
||||
<div class="stat-card stat-total">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Files /></el-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ stats.total }}</div>
|
||||
<div class="stat-label">总配置数</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<div class="stats-row">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-statistic title="总配置数" :value="stats.total" />
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="已启用" :value="stats.enabled" />
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="已禁用" :value="stats.disabled" />
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="默认配置" :value="stats.default" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<el-table :data="tableData" v-loading="loading" stripe>
|
||||
<el-table-column prop="configName" label="配置名称" width="150" />
|
||||
<el-table-column prop="configKey" label="配置键值" width="150" />
|
||||
<el-table-column prop="configType" label="配置类型" width="100">
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card stat-enabled">
|
||||
<div class="stat-icon">
|
||||
<el-icon><CircleCheck /></el-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ stats.enabled }}</div>
|
||||
<div class="stat-label">已启用</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card stat-disabled">
|
||||
<div class="stat-icon">
|
||||
<el-icon><CircleClose /></el-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ stats.disabled }}</div>
|
||||
<div class="stat-label">已禁用</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card stat-default">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Star /></el-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ stats.default }}</div>
|
||||
<div class="stat-label">默认配置</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card class="table-card" shadow="never">
|
||||
<el-table :data="tableData" v-loading="loading" stripe class="data-table">
|
||||
<el-table-column prop="configName" label="配置名称" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="configKey" label="配置键值" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="configType" label="类型" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getConfigTypeTagType(row.configType)">
|
||||
<el-tag :type="getConfigTypeTagType(row.configType)" effect="plain" size="small">
|
||||
{{ getConfigTypeLabel(row.configType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="provider" label="服务提供商" width="120">
|
||||
<el-table-column prop="provider" label="服务商" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getProviderTagType(row.provider)">
|
||||
<el-tag :type="getProviderTagType(row.provider)" effect="plain" size="small">
|
||||
{{ getProviderLabel(row.provider) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="usageScenario" label="使用场景" width="120">
|
||||
<el-table-column prop="usageScenario" label="使用场景" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="info">
|
||||
<el-tag type="info" effect="plain" size="small">
|
||||
{{ getUsageScenarioLabel(row.usageScenario) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="environment" label="环境" width="100">
|
||||
<el-table-column prop="environment" label="环境" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getEnvironmentTagType(row.environment)">
|
||||
<el-tag :type="getEnvironmentTagType(row.environment)" effect="plain" size="small">
|
||||
{{ getEnvironmentLabel(row.environment) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="priority" label="优先级" width="80" />
|
||||
<el-table-column prop="isEnabled" label="状态" width="80">
|
||||
<el-table-column prop="isEnabled" label="状态" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.isEnabled === 1 ? 'success' : 'danger'">
|
||||
<el-tag :type="row.isEnabled === 1 ? 'success' : 'danger'" effect="plain" size="small">
|
||||
{{ row.isEnabled === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isDefault" label="默认" width="80">
|
||||
<el-table-column prop="isDefault" label="默认" width="70" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.isDefault === 1" type="warning">默认</el-tag>
|
||||
<span v-else>-</span>
|
||||
<el-tag v-if="row.isDefault === 1" type="warning" effect="dark" size="small">默认</el-tag>
|
||||
<span v-else class="dash">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="150" />
|
||||
<el-table-column label="操作" width="320" fixed="right">
|
||||
<el-table-column label="操作" width="260" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="info" link @click="handleView(row)">查看</el-button>
|
||||
<el-button type="success" link @click="handleTest(row)">测试</el-button>
|
||||
<el-button
|
||||
:type="row.isEnabled === 1 ? 'warning' : 'success'"
|
||||
link
|
||||
@click="handleToggleStatus(row)"
|
||||
>
|
||||
{{ row.isEnabled === 1 ? '禁用' : '启用' }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.isDefault !== 1"
|
||||
type="warning"
|
||||
link
|
||||
@click="handleSetDefault(row)"
|
||||
>
|
||||
设为默认
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="info"
|
||||
link
|
||||
@click="handleUnsetDefault(row)"
|
||||
>
|
||||
取消默认
|
||||
</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
<div class="action-group">
|
||||
<el-button type="primary" link size="small" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="primary" link size="small" @click="handleView(row)">查看</el-button>
|
||||
<el-button type="success" link size="small" @click="handleTest(row)">测试</el-button>
|
||||
<el-divider direction="vertical" class="action-divider" />
|
||||
<el-button
|
||||
:type="row.isEnabled === 1 ? 'warning' : 'success'"
|
||||
link
|
||||
size="small"
|
||||
@click="handleToggleStatus(row)"
|
||||
>
|
||||
{{ row.isEnabled === 1 ? '禁用' : '启用' }}
|
||||
</el-button>
|
||||
<el-button
|
||||
:type="row.isDefault === 1 ? 'info' : 'warning'"
|
||||
link
|
||||
size="small"
|
||||
@click="row.isDefault === 1 ? handleUnsetDefault(row) : handleSetDefault(row)"
|
||||
>
|
||||
{{ row.isDefault === 1 ? '取消默认' : '设为默认' }}
|
||||
</el-button>
|
||||
<el-divider direction="vertical" class="action-divider" />
|
||||
<el-button type="danger" link size="small" @click="handleDelete(row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.current"
|
||||
v-model:page-size="pagination.size"
|
||||
:total="pagination.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="fetchData"
|
||||
@current-change="fetchData"
|
||||
class="pagination"
|
||||
/>
|
||||
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.current"
|
||||
v-model:page-size="pagination.size"
|
||||
:total="pagination.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="fetchData"
|
||||
@current-change="fetchData"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
@@ -692,7 +711,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { InfoFilled } from '@element-plus/icons-vue'
|
||||
import { InfoFilled, Plus, Files, CircleCheck, CircleClose, Star } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getAiConfigPage,
|
||||
createAiConfig,
|
||||
@@ -936,11 +955,6 @@ const handleReset = () => {
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 刷新统计
|
||||
const handleRefreshStats = () => {
|
||||
fetchStats()
|
||||
}
|
||||
|
||||
// 新增
|
||||
const handleAdd = () => {
|
||||
isEdit.value = false
|
||||
@@ -1578,53 +1592,223 @@ onMounted(() => {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ai-config-list {
|
||||
.page-title {
|
||||
margin-bottom: 20px;
|
||||
font-size: 24px;
|
||||
color: var(--ls-text);
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
// 页面头部
|
||||
.page-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 10px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.header-left {
|
||||
.page-title {
|
||||
margin: 0 0 6px;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: var(--ls-text, #e2e8f0);
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.page-desc {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: var(--ls-text-secondary, rgba(226, 232, 240, 0.6));
|
||||
}
|
||||
}
|
||||
|
||||
.stats-row {
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-radius: var(--ls-radius-lg);
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
justify-content: flex-end;
|
||||
|
||||
.header-actions {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 搜索卡片
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
gap: 0;
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 8px;
|
||||
margin-right: 16px;
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper),
|
||||
:deep(.el-select__wrapper) {
|
||||
min-width: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
margin-left: auto;
|
||||
|
||||
.el-button {
|
||||
min-width: 64px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 统计卡片行
|
||||
.stats-row {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.stat-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px 24px;
|
||||
border-radius: 12px;
|
||||
background: var(--ls-card-bg, rgba(30, 41, 59, 0.6));
|
||||
border: 1px solid var(--ls-border, rgba(255, 255, 255, 0.06));
|
||||
transition: all 0.2s ease;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
margin-right: 16px;
|
||||
|
||||
:deep(.el-icon) {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.stat-value {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
color: var(--ls-text-secondary, rgba(226, 232, 240, 0.55));
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// 各统计卡片配色
|
||||
&.stat-total {
|
||||
.stat-icon {
|
||||
flex-shrink: 0;
|
||||
background: rgba(99, 102, 241, 0.15);
|
||||
color: #818cf8;
|
||||
}
|
||||
.stat-value {
|
||||
color: #818cf8;
|
||||
}
|
||||
}
|
||||
|
||||
&.stat-enabled {
|
||||
.stat-icon {
|
||||
flex-shrink: 0;
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
color: #4ade80;
|
||||
}
|
||||
.stat-value {
|
||||
color: #4ade80;
|
||||
}
|
||||
}
|
||||
|
||||
&.stat-disabled {
|
||||
.stat-icon {
|
||||
flex-shrink: 0;
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
color: #f87171;
|
||||
}
|
||||
.stat-value {
|
||||
color: #f87171;
|
||||
}
|
||||
}
|
||||
|
||||
&.stat-default {
|
||||
.stat-icon {
|
||||
flex-shrink: 0;
|
||||
background: rgba(251, 191, 36, 0.15);
|
||||
color: #fbbf24;
|
||||
}
|
||||
.stat-value {
|
||||
color: #fbbf24;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 数据表格
|
||||
.table-card {
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
:deep(.el-table__header) th {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: var(--ls-text-secondary, rgba(226, 232, 240, 0.7));
|
||||
background: var(--ls-table-header-bg, rgba(30, 41, 59, 0.4));
|
||||
}
|
||||
|
||||
:deep(.el-table__row) {
|
||||
transition: background 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--ls-table-hover-bg, rgba(99, 102, 241, 0.06)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dash {
|
||||
color: var(--ls-text-secondary, rgba(226, 232, 240, 0.3));
|
||||
}
|
||||
}
|
||||
|
||||
// 操作按钮分组
|
||||
.action-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
|
||||
.action-divider {
|
||||
margin: 0 2px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 分页
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px;
|
||||
border-top: 1px solid var(--ls-border, rgba(255, 255, 255, 0.06));
|
||||
}
|
||||
|
||||
// 表单提示
|
||||
.form-tip {
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
@@ -1632,6 +1816,7 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// 测试容器
|
||||
.test-container {
|
||||
.test-section {
|
||||
h4 {
|
||||
@@ -1640,18 +1825,18 @@ onMounted(() => {
|
||||
border-bottom: 2px solid rgba(255, 171, 145, 0.6);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
|
||||
.test-options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
|
||||
.info-icon {
|
||||
color: rgba(226, 232, 240, 0.6);
|
||||
cursor: help;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.el-textarea {
|
||||
:deep(.el-textarea__inner) {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
@@ -1659,7 +1844,7 @@ onMounted(() => {
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.el-input {
|
||||
:deep(.el-input__inner) {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
|
||||
Reference in New Issue
Block a user