213
This commit is contained in:
parent
fae900819a
commit
82dd3e731b
|
|
@ -0,0 +1,96 @@
|
|||
using MiAssessment.Admin.Filters;
|
||||
using MiAssessment.Admin.Models.Common;
|
||||
using MiAssessment.Admin.Models.Config;
|
||||
using MiAssessment.Admin.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace MiAssessment.Admin.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 后台配置管理控制器
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/admin/config")]
|
||||
[Authorize]
|
||||
public class ConfigController : ControllerBase
|
||||
{
|
||||
private readonly IAdminConfigService _configService;
|
||||
private readonly ILogger<ConfigController> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// 配置键常量
|
||||
/// </summary>
|
||||
private static class ConfigKeys
|
||||
{
|
||||
public const string Upload = "upload_setting";
|
||||
}
|
||||
|
||||
public ConfigController(IAdminConfigService configService, ILogger<ConfigController> logger)
|
||||
{
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取上传配置
|
||||
/// </summary>
|
||||
/// <returns>上传配置</returns>
|
||||
[HttpGet("upload/get")]
|
||||
public async Task<ApiResponse<UploadSetting>> GetUploadConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = await _configService.GetConfigAsync<UploadSetting>(ConfigKeys.Upload);
|
||||
return ApiResponse<UploadSetting>.Success(config ?? new UploadSetting { Type = "1" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取上传配置失败");
|
||||
return ApiResponse<UploadSetting>.Error(AdminErrorCodes.InternalError, "获取上传配置失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新上传配置
|
||||
/// </summary>
|
||||
/// <param name="request">上传配置</param>
|
||||
/// <returns>更新结果</returns>
|
||||
[HttpPost("upload/update")]
|
||||
[OperationLog("配置管理", "更新上传配置")]
|
||||
public async Task<ApiResponse<bool>> UpdateUploadConfig([FromBody] UploadSetting request)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Type))
|
||||
{
|
||||
return ApiResponse<bool>.Error(AdminErrorCodes.InvalidParameter, "存储类型不能为空");
|
||||
}
|
||||
|
||||
// 验证腾讯云COS配置
|
||||
if (request.Type == "3")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.AppId))
|
||||
return ApiResponse<bool>.Error(AdminErrorCodes.InvalidParameter, "AppId不能为空");
|
||||
if (string.IsNullOrWhiteSpace(request.AccessKeyId))
|
||||
return ApiResponse<bool>.Error(AdminErrorCodes.InvalidParameter, "SecretId不能为空");
|
||||
if (string.IsNullOrWhiteSpace(request.AccessKeySecret))
|
||||
return ApiResponse<bool>.Error(AdminErrorCodes.InvalidParameter, "SecretKey不能为空");
|
||||
if (string.IsNullOrWhiteSpace(request.Bucket))
|
||||
return ApiResponse<bool>.Error(AdminErrorCodes.InvalidParameter, "存储桶名称不能为空");
|
||||
if (string.IsNullOrWhiteSpace(request.Region))
|
||||
return ApiResponse<bool>.Error(AdminErrorCodes.InvalidParameter, "地域不能为空");
|
||||
if (string.IsNullOrWhiteSpace(request.Domain))
|
||||
return ApiResponse<bool>.Error(AdminErrorCodes.InvalidParameter, "访问域名不能为空");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _configService.UpdateConfigAsync(ConfigKeys.Upload, request);
|
||||
return ApiResponse<bool>.Success(result, "配置更新成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "更新上传配置失败");
|
||||
return ApiResponse<bool>.Error(AdminErrorCodes.InternalError, "更新上传配置失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,6 +51,7 @@ public static class ServiceCollectionExtensions
|
|||
services.AddScoped<IPermissionService, PermissionService>();
|
||||
services.AddScoped<IOperationLogService, OperationLogService>();
|
||||
services.AddScoped<IDictService, DictService>();
|
||||
services.AddScoped<IAdminConfigService, AdminConfigService>();
|
||||
services.AddScoped<IDataSeeder, DataSeeder>();
|
||||
services.AddSingleton<ICaptchaService, CaptchaService>();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
namespace MiAssessment.Admin.Models.Config;
|
||||
|
||||
/// <summary>
|
||||
/// 上传配置
|
||||
/// </summary>
|
||||
public class UploadSetting
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储类型 1本地 2阿里云 3腾讯云
|
||||
/// </summary>
|
||||
public string Type { get; set; } = "1";
|
||||
|
||||
/// <summary>
|
||||
/// 腾讯云AppId
|
||||
/// </summary>
|
||||
public string? AppId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 存储桶名称
|
||||
/// </summary>
|
||||
public string? Bucket { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 地域
|
||||
/// </summary>
|
||||
public string? Region { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SecretId
|
||||
/// </summary>
|
||||
public string? AccessKeyId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SecretKey
|
||||
/// </summary>
|
||||
public string? AccessKeySecret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 访问域名
|
||||
/// </summary>
|
||||
public string? Domain { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using MiAssessment.Admin.Data;
|
||||
using MiAssessment.Admin.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MiAssessment.Admin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 后台配置服务实现
|
||||
/// </summary>
|
||||
public class AdminConfigService : IAdminConfigService
|
||||
{
|
||||
private readonly AdminDbContext _dbContext;
|
||||
private readonly ILogger<AdminConfigService> _logger;
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
public AdminConfigService(AdminDbContext dbContext, ILogger<AdminConfigService> logger)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string?> GetConfigAsync(string key)
|
||||
{
|
||||
var config = await _dbContext.AdminConfigs
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(c => c.ConfigKey == key);
|
||||
return config?.ConfigValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<T?> GetConfigAsync<T>(string key) where T : class
|
||||
{
|
||||
var value = await GetConfigAsync(key);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<T>(value, JsonOptions);
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "反序列化配置失败: {Key}", key);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> UpdateConfigAsync(string key, string value)
|
||||
{
|
||||
var config = await _dbContext.AdminConfigs
|
||||
.FirstOrDefaultAsync(c => c.ConfigKey == key);
|
||||
|
||||
if (config == null)
|
||||
{
|
||||
// 创建新配置
|
||||
config = new AdminConfig
|
||||
{
|
||||
ConfigKey = key,
|
||||
ConfigValue = value,
|
||||
CreatedAt = DateTime.Now
|
||||
};
|
||||
_dbContext.AdminConfigs.Add(config);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 更新现有配置
|
||||
config.ConfigValue = value;
|
||||
config.UpdatedAt = DateTime.Now;
|
||||
}
|
||||
|
||||
await _dbContext.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> UpdateConfigAsync<T>(string key, T value) where T : class
|
||||
{
|
||||
var jsonValue = JsonSerializer.Serialize(value, JsonOptions);
|
||||
return await UpdateConfigAsync(key, jsonValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace MiAssessment.Admin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 后台配置服务接口
|
||||
/// </summary>
|
||||
public interface IAdminConfigService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取配置值
|
||||
/// </summary>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <returns>配置值</returns>
|
||||
Task<string?> GetConfigAsync(string key);
|
||||
|
||||
/// <summary>
|
||||
/// 获取配置值并反序列化
|
||||
/// </summary>
|
||||
/// <typeparam name="T">配置类型</typeparam>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <returns>配置对象</returns>
|
||||
Task<T?> GetConfigAsync<T>(string key) where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 更新配置值
|
||||
/// </summary>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="value">配置值</param>
|
||||
/// <returns>是否成功</returns>
|
||||
Task<bool> UpdateConfigAsync(string key, string value);
|
||||
|
||||
/// <summary>
|
||||
/// 更新配置值(序列化对象)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">配置类型</typeparam>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="value">配置对象</param>
|
||||
/// <returns>是否成功</returns>
|
||||
Task<bool> UpdateConfigAsync<T>(string key, T value) where T : class;
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* System Config API - 系统配置 API
|
||||
* @module api/system/config
|
||||
*/
|
||||
import { request, type ApiResponse } from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 上传配置
|
||||
*/
|
||||
export interface UploadSetting {
|
||||
/** 存储类型 1本地 2阿里云 3腾讯云 */
|
||||
type: string
|
||||
/** 腾讯云AppId */
|
||||
AppId?: string
|
||||
/** 存储桶名称 */
|
||||
Bucket?: string
|
||||
/** 地域 */
|
||||
Region?: string
|
||||
/** SecretId */
|
||||
AccessKeyId?: string
|
||||
/** SecretKey */
|
||||
AccessKeySecret?: string
|
||||
/** 访问域名 */
|
||||
Domain?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传配置
|
||||
*/
|
||||
export function getUploadConfig(): Promise<ApiResponse<UploadSetting>> {
|
||||
return request<UploadSetting>({
|
||||
url: '/admin/config/upload/get',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新上传配置
|
||||
*/
|
||||
export function updateUploadConfig(data: UploadSetting): Promise<ApiResponse<boolean>> {
|
||||
return request<boolean>({
|
||||
url: '/admin/config/upload/update',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ import type { RouteRecordRaw } from 'vue-router'
|
|||
import { getUserMenus, type MenuTree } from '@/api/menu'
|
||||
import Layout from '@/layout/index.vue'
|
||||
|
||||
// 视图模块映射
|
||||
const viewModules = import.meta.glob('@/views/**/*.vue')
|
||||
// 视图模块映射 - 使用相对路径格式
|
||||
const viewModules = import.meta.glob('/src/views/**/*.vue')
|
||||
|
||||
export const usePermissionStore = defineStore('permission', () => {
|
||||
const routes = ref<RouteRecordRaw[]>([])
|
||||
|
|
@ -82,7 +82,13 @@ export const usePermissionStore = defineStore('permission', () => {
|
|||
// 加载组件
|
||||
function loadComponent(component: string) {
|
||||
const path = `/src/views/${component}.vue`
|
||||
return viewModules[path] || (() => import('@/views/error/404.vue'))
|
||||
|
||||
if (viewModules[path]) {
|
||||
return viewModules[path]
|
||||
}
|
||||
|
||||
console.warn(`Component not found: ${component}, path: ${path}`)
|
||||
return () => import('@/views/error/404.vue')
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
|
|
|
|||
|
|
@ -0,0 +1,356 @@
|
|||
<template>
|
||||
<div class="upload-config-container">
|
||||
<!-- 页面标题 -->
|
||||
<el-card class="page-header">
|
||||
<div class="header-content">
|
||||
<h2 class="page-title">上传配置</h2>
|
||||
<span class="page-description">配置文件上传存储方式,支持本地存储和腾讯云COS</span>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 配置表单 -->
|
||||
<el-card v-loading="state.loading" class="config-form-card">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="state.formData"
|
||||
:rules="formRules"
|
||||
label-width="140px"
|
||||
label-position="right"
|
||||
>
|
||||
<!-- 存储类型选择 -->
|
||||
<el-form-item label="存储类型" prop="type">
|
||||
<el-radio-group v-model="state.formData.type" @change="handleTypeChange">
|
||||
<el-radio value="1">
|
||||
<div class="storage-option">
|
||||
<el-icon><FolderOpened /></el-icon>
|
||||
<span>本地存储</span>
|
||||
</div>
|
||||
</el-radio>
|
||||
<el-radio value="3">
|
||||
<div class="storage-option">
|
||||
<el-icon><Cloudy /></el-icon>
|
||||
<span>腾讯云COS</span>
|
||||
</div>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 本地存储说明 -->
|
||||
<el-alert
|
||||
v-if="state.formData.type === '1'"
|
||||
title="本地存储说明"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="storage-tip"
|
||||
>
|
||||
文件将存储在服务器本地 uploads 目录,适合小型项目或测试环境。
|
||||
</el-alert>
|
||||
|
||||
<!-- 腾讯云COS配置 -->
|
||||
<template v-if="state.formData.type === '3'">
|
||||
<el-divider content-position="left">腾讯云COS配置</el-divider>
|
||||
|
||||
<el-alert
|
||||
title="配置说明"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="storage-tip"
|
||||
>
|
||||
请前往腾讯云控制台获取相关配置信息。确保存储桶已开启跨域访问(CORS)。
|
||||
</el-alert>
|
||||
|
||||
<el-form-item label="AppId" prop="AppId">
|
||||
<el-input
|
||||
v-model="state.formData.AppId"
|
||||
placeholder="请输入腾讯云AppId"
|
||||
clearable
|
||||
/>
|
||||
<div class="form-item-tip">腾讯云账号的AppId,可在账号信息中查看</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="SecretId" prop="AccessKeyId">
|
||||
<el-input
|
||||
v-model="state.formData.AccessKeyId"
|
||||
placeholder="请输入SecretId"
|
||||
clearable
|
||||
/>
|
||||
<div class="form-item-tip">API密钥的SecretId</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="SecretKey" prop="AccessKeySecret">
|
||||
<el-input
|
||||
v-model="state.formData.AccessKeySecret"
|
||||
placeholder="请输入SecretKey"
|
||||
type="password"
|
||||
show-password
|
||||
clearable
|
||||
/>
|
||||
<div class="form-item-tip">API密钥的SecretKey,请妥善保管</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="存储桶名称" prop="Bucket">
|
||||
<el-input
|
||||
v-model="state.formData.Bucket"
|
||||
placeholder="请输入存储桶名称,如 my-bucket-1250000000"
|
||||
clearable
|
||||
/>
|
||||
<div class="form-item-tip">完整的存储桶名称,包含AppId后缀</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="地域" prop="Region">
|
||||
<el-select v-model="state.formData.Region" placeholder="请选择地域" clearable>
|
||||
<el-option label="北京 (ap-beijing)" value="ap-beijing" />
|
||||
<el-option label="上海 (ap-shanghai)" value="ap-shanghai" />
|
||||
<el-option label="广州 (ap-guangzhou)" value="ap-guangzhou" />
|
||||
<el-option label="成都 (ap-chengdu)" value="ap-chengdu" />
|
||||
<el-option label="重庆 (ap-chongqing)" value="ap-chongqing" />
|
||||
<el-option label="南京 (ap-nanjing)" value="ap-nanjing" />
|
||||
<el-option label="香港 (ap-hongkong)" value="ap-hongkong" />
|
||||
<el-option label="新加坡 (ap-singapore)" value="ap-singapore" />
|
||||
</el-select>
|
||||
<div class="form-item-tip">存储桶所在地域</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="访问域名" prop="Domain">
|
||||
<el-input
|
||||
v-model="state.formData.Domain"
|
||||
placeholder="请输入CDN加速域名或存储桶域名"
|
||||
clearable
|
||||
>
|
||||
<template #prepend>https://</template>
|
||||
</el-input>
|
||||
<div class="form-item-tip">用于访问文件的域名,可使用CDN加速域名</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="state.saving" @click="handleSave">
|
||||
<el-icon><Check /></el-icon>
|
||||
保存配置
|
||||
</el-button>
|
||||
<el-button @click="handleReset">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
重置
|
||||
</el-button>
|
||||
<el-button v-if="state.formData.type === '3'" @click="handleTest">
|
||||
<el-icon><Connection /></el-icon>
|
||||
测试连接
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 上传配置页面
|
||||
* @description 配置文件上传存储方式
|
||||
*/
|
||||
import { reactive, ref, computed, onMounted } from 'vue'
|
||||
import { FolderOpened, Cloudy, Check, Refresh, Connection } from '@element-plus/icons-vue'
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { getUploadConfig, updateUploadConfig, type UploadSetting } from '@/api/system/config'
|
||||
|
||||
// ============ Types ============
|
||||
|
||||
interface UploadConfigState {
|
||||
loading: boolean
|
||||
saving: boolean
|
||||
formData: UploadSetting
|
||||
}
|
||||
|
||||
// ============ Refs ============
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
// ============ State ============
|
||||
|
||||
const state = reactive<UploadConfigState>({
|
||||
loading: false,
|
||||
saving: false,
|
||||
formData: {
|
||||
type: '1',
|
||||
AppId: '',
|
||||
Bucket: '',
|
||||
Region: '',
|
||||
AccessKeyId: '',
|
||||
AccessKeySecret: '',
|
||||
Domain: ''
|
||||
}
|
||||
})
|
||||
|
||||
// ============ Form Rules ============
|
||||
|
||||
const formRules = computed<FormRules>(() => {
|
||||
const rules: FormRules = {
|
||||
type: [{ required: true, message: '请选择存储类型', trigger: 'change' }]
|
||||
}
|
||||
|
||||
// 腾讯云COS必填验证
|
||||
if (state.formData.type === '3') {
|
||||
rules.AppId = [{ required: true, message: '请输入AppId', trigger: 'blur' }]
|
||||
rules.AccessKeyId = [{ required: true, message: '请输入SecretId', trigger: 'blur' }]
|
||||
rules.AccessKeySecret = [{ required: true, message: '请输入SecretKey', trigger: 'blur' }]
|
||||
rules.Bucket = [{ required: true, message: '请输入存储桶名称', trigger: 'blur' }]
|
||||
rules.Region = [{ required: true, message: '请选择地域', trigger: 'change' }]
|
||||
rules.Domain = [{ required: true, message: '请输入访问域名', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
return rules
|
||||
})
|
||||
|
||||
// ============ API Functions ============
|
||||
|
||||
async function loadConfig() {
|
||||
state.loading = true
|
||||
try {
|
||||
const res = await getUploadConfig()
|
||||
if (res.code === 0 && res.data) {
|
||||
state.formData = {
|
||||
type: res.data.type || '1',
|
||||
AppId: res.data.AppId || '',
|
||||
Bucket: res.data.Bucket || '',
|
||||
Region: res.data.Region || '',
|
||||
AccessKeyId: res.data.AccessKeyId || '',
|
||||
AccessKeySecret: res.data.AccessKeySecret || '',
|
||||
Domain: res.data.Domain || ''
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('获取配置失败')
|
||||
} finally {
|
||||
state.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Event Handlers ============
|
||||
|
||||
function handleTypeChange() {
|
||||
// 切换类型时清除验证
|
||||
formRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (!formRef.value) return
|
||||
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
state.saving = true
|
||||
try {
|
||||
const res = await updateUploadConfig(state.formData)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('配置保存成功')
|
||||
} else {
|
||||
throw new Error(res.message || '保存失败')
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : '保存失败'
|
||||
ElMessage.error(message)
|
||||
} finally {
|
||||
state.saving = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
loadConfig()
|
||||
}
|
||||
|
||||
function handleTest() {
|
||||
// TODO: 实现连接测试
|
||||
ElMessage.info('连接测试功能开发中')
|
||||
}
|
||||
|
||||
// ============ Lifecycle ============
|
||||
|
||||
onMounted(() => {
|
||||
loadConfig()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.upload-config-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #303133);
|
||||
}
|
||||
|
||||
.page-description {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary, #909399);
|
||||
}
|
||||
|
||||
.config-form-card {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.config-form-card :deep(.el-card__body) {
|
||||
padding: 30px 40px;
|
||||
}
|
||||
|
||||
.storage-option {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.storage-tip {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-item-tip {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
line-height: 1.5;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-radio) {
|
||||
height: auto;
|
||||
padding: 12px 20px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 8px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
:deep(.el-radio.is-checked) {
|
||||
border-color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
}
|
||||
|
||||
:deep(.el-divider__text) {
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
:deep(.el-input),
|
||||
:deep(.el-select) {
|
||||
max-width: 400px;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user