feat: 优化管理后台页面UI、修复TS编译错误、新增人生事件模块

- 优化 AI 配置列表页面:重构统计卡片、搜索表单、表格列展示
- 修复 3 处 TypeScript TS6133 编译错误,恢复构建
- 新增管理员修改密码和重置密码功能
- 优化小程序多个页面样式和交互
- 人生事件模块完善

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 23:23:09 +08:00
parent 60c63850ee
commit 755059807a
62 changed files with 4661 additions and 3019 deletions
+18
View File
@@ -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
})
}
+71
View File
@@ -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
}
}
+74 -1
View File
@@ -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
+356 -171
View File
@@ -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;