Files
kra-new/web/src/view/systemTools/system/system.vue
T

260 lines
14 KiB
Vue

<template>
<div class="kra-table-box config-layout">
<div class="flex items-center justify-between mb-5">
<div>
<h2 class="text-xl font-semibold">系统配置</h2>
<p class="text-sm text-gray-500 mt-1">管理 Kra 后台运行参数和邮件服务。</p>
</div>
<div class="flex gap-2">
<el-button @click="reload">重载服务</el-button>
<el-button type="primary" :loading="saving" @click="update">保存配置</el-button>
</div>
</div>
<el-tabs v-model="activeKey">
<el-tab-pane label="基础设置" name="basic">
<el-form label-position="top" class="grid grid-cols-1 md:grid-cols-2 gap-x-5">
<el-form-item label="路由前缀">
<el-input v-model.trim="config.admin.routerPrefix" placeholder="例如 /api;留空表示无前缀" />
</el-form-item>
<el-form-item label="JWT 签发者">
<el-input v-model.trim="config.admin.jwt.issuer" />
</el-form-item>
<el-form-item label="JWT 签名密钥">
<el-input v-model="config.admin.jwt.signingKey" show-password />
</el-form-item>
<el-form-item label="JWT 有效期">
<el-input v-model.trim="config.admin.jwt.expiresTime" placeholder="例如 168h" />
</el-form-item>
<el-form-item label="JWT 缓冲期">
<el-input v-model.trim="config.admin.jwt.bufferTime" placeholder="例如 24h" />
</el-form-item>
<el-form-item label="验证码长度">
<el-input-number v-model="config.admin.captcha.keyLong" :min="1" :max="12" class="!w-full" />
</el-form-item>
<el-form-item label="验证码宽度">
<el-input-number v-model="config.admin.captcha.imgWidth" :min="40" class="!w-full" />
</el-form-item>
<el-form-item label="验证码高度">
<el-input-number v-model="config.admin.captcha.imgHeight" :min="20" class="!w-full" />
</el-form-item>
<el-form-item label="验证码缓存时间">
<el-input v-model.trim="config.admin.captcha.storeExpiration" placeholder="例如 3m" />
</el-form-item>
<el-form-item label="本地文件目录">
<el-input v-model.trim="config.admin.local.storePath" />
</el-form-item>
<el-form-item label="文件访问前缀">
<el-input v-model.trim="config.admin.local.pathPrefix" />
</el-form-item>
<el-form-item label="大文件上传会话有效期(小时)">
<el-input-number v-model="config.admin.media.sessionTtl" :min="1" :max="720" class="!w-full" />
</el-form-item>
<el-form-item label="运行开关" class="md:col-span-2">
<div class="flex flex-wrap gap-5">
<el-switch v-model="config.admin.system.useRedis" active-text="启用 Redis" />
<el-switch v-model="config.admin.system.useMultipoint" active-text="限制多点登录" />
<el-switch v-model="config.admin.system.useStrictAuth" active-text="严格角色树" />
<el-switch v-model="config.admin.system.disableAutoMigrate" active-text="禁用自动迁移" />
</div>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="数据库" name="database">
<el-form label-position="top" class="grid grid-cols-1 md:grid-cols-2 gap-x-5">
<el-form-item label="数据库类型">
<el-select v-model="config.data.database.driver" class="!w-full">
<el-option label="MySQL" value="mysql" /><el-option label="PostgreSQL" value="pgsql" />
<el-option label="SQL Server" value="mssql" /><el-option label="SQLite" value="sqlite" />
<el-option label="Oracle" value="oracle" />
</el-select>
</el-form-item>
<el-form-item label="主机"><el-input v-model.trim="config.data.database.host" /></el-form-item>
<el-form-item label="端口"><el-input v-model.trim="config.data.database.port" /></el-form-item>
<el-form-item label="用户名"><el-input v-model.trim="config.data.database.user" /></el-form-item>
<el-form-item label="密码"><el-input v-model="config.data.database.password" show-password /></el-form-item>
<el-form-item label="数据库名"><el-input v-model.trim="config.data.database.name" /></el-form-item>
<el-form-item label="SQLite 目录"><el-input v-model.trim="config.data.database.path" /></el-form-item>
<el-form-item label="连接参数"><el-input v-model.trim="config.data.database.config" /></el-form-item>
<p class="md:col-span-2 text-sm text-gray-500">保存后点击“重载服务”,数据库连接会按新配置重建。</p>
</el-form>
</el-tab-pane>
<el-tab-pane label="Redis" name="redis">
<el-form label-position="top" class="grid grid-cols-1 md:grid-cols-2 gap-x-5">
<el-form-item label="网络"><el-input v-model.trim="config.data.redis.network" placeholder="tcp" /></el-form-item>
<el-form-item label="地址"><el-input v-model.trim="config.data.redis.addr" placeholder="127.0.0.1:6379;留空关闭 Redis" /></el-form-item>
<el-form-item label="读取超时"><el-input v-model.trim="config.data.redis.read_timeout" placeholder="例如 200ms" /></el-form-item>
<el-form-item label="写入超时"><el-input v-model.trim="config.data.redis.write_timeout" placeholder="例如 200ms" /></el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="对象存储" name="storage">
<el-form label-position="top" class="grid grid-cols-1 md:grid-cols-2 gap-x-5">
<el-form-item label="存储类型" class="md:col-span-2">
<el-select v-model="config.admin.storage.type" class="!w-full">
<el-option v-for="item in storageTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<template v-if="config.admin.storage.type === 'qiniu'">
<el-form-item label="区域"><el-input v-model.trim="config.admin.storage.qiniu.zone" placeholder="ZoneHuadong" /></el-form-item>
<el-form-item label="Bucket"><el-input v-model.trim="config.admin.storage.qiniu.bucket" /></el-form-item>
<el-form-item label="访问域名"><el-input v-model.trim="config.admin.storage.qiniu.base_url" /></el-form-item>
<el-form-item label="Access Key"><el-input v-model.trim="config.admin.storage.qiniu.access_key" /></el-form-item>
<el-form-item label="Secret Key"><el-input v-model="config.admin.storage.qiniu.secret_key" show-password /></el-form-item>
<el-form-item label="连接选项"><div class="flex gap-5"><el-switch v-model="config.admin.storage.qiniu.use_https" active-text="HTTPS" /><el-switch v-model="config.admin.storage.qiniu.use_cdn_domains" active-text="CDN 域名" /></div></el-form-item>
</template>
<template v-else-if="currentObjectStorage">
<el-form-item label="Endpoint"><el-input v-model.trim="currentObjectStorage.endpoint" placeholder="可留空使用提供商默认地址" /></el-form-item>
<el-form-item label="Region"><el-input v-model.trim="currentObjectStorage.region" /></el-form-item>
<el-form-item label="Bucket"><el-input v-model.trim="currentObjectStorage.bucket" /></el-form-item>
<el-form-item label="访问域名"><el-input v-model.trim="currentObjectStorage.base_url" /></el-form-item>
<el-form-item label="Access Key"><el-input v-model.trim="currentObjectStorage.access_key" /></el-form-item>
<el-form-item label="Secret Key"><el-input v-model="currentObjectStorage.secret_key" show-password /></el-form-item>
<el-form-item label="对象前缀"><el-input v-model.trim="currentObjectStorage.path_prefix" /></el-form-item>
<el-form-item v-if="config.admin.storage.type === 'cloudflare-r2'" label="Account ID"><el-input v-model.trim="currentObjectStorage.account_id" /></el-form-item>
<el-form-item label="连接选项"><div class="flex gap-5"><el-switch v-model="currentObjectStorage.use_ssl" active-text="HTTPS" /><el-switch v-model="currentObjectStorage.force_path_style" active-text="Path Style" /></div></el-form-item>
</template>
<p class="md:col-span-2 text-sm text-gray-500">保存后点击“重载服务”,新的对象存储配置会立即生效。</p>
</el-form>
</el-tab-pane>
<el-tab-pane label="邮件设置" name="email">
<el-form label-position="top" class="grid grid-cols-1 md:grid-cols-2 gap-x-5">
<el-form-item label="默认收件人">
<el-input v-model.trim="config.email.to" placeholder="多个地址用英文逗号分隔" />
</el-form-item>
<el-form-item label="发件人">
<el-input v-model.trim="config.email.from" />
</el-form-item>
<el-form-item label="SMTP 主机">
<el-input v-model.trim="config.email.host" />
</el-form-item>
<el-form-item label="SMTP 端口">
<el-input-number v-model="config.email.port" :min="1" :max="65535" class="!w-full" />
</el-form-item>
<el-form-item label="SMTP 密钥">
<el-input v-model="config.email.secret" show-password />
</el-form-item>
<el-form-item label="发件人昵称">
<el-input v-model.trim="config.email.nickname" />
</el-form-item>
<el-form-item label="连接方式">
<div class="flex gap-5">
<el-switch v-model="config.email['is-ssl']" active-text="SSL" />
<el-switch v-model="config.email['is-loginauth']" active-text="LOGIN 认证" />
</div>
</el-form-item>
<el-form-item label="连通性测试">
<el-button :loading="testing" @click="testEmail">发送测试邮件</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup>
import { computed, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getSystemConfig, reloadSystem, setSystemConfig } from '@/api/system'
import { emailTest } from '@/api/email'
defineOptions({ name: 'Config' })
const activeKey = ref('basic')
const saving = ref(false)
const testing = ref(false)
const config = ref({
admin: {
routerPrefix: '',
jwt: { signingKey: '******', expiresTime: '168h', bufferTime: '24h', issuer: 'kra' },
captcha: { keyLong: 6, imgWidth: 240, imgHeight: 80, storeExpiration: '3m' },
local: { storePath: 'uploads/file', pathPrefix: 'uploads/file' }, media: { sessionTtl: 24 },
system: { useRedis: false, useMultipoint: false, useStrictAuth: false, disableAutoMigrate: false },
storage: { type: 'local', qiniu: {}, aliyun_oss: {}, huawei_obs: {}, tencent_cos: {}, aws_s3: {}, cloudflare_r2: {}, minio: {} }
},
email: {
to: '', from: '', host: '', secret: '******', nickname: '', port: 465,
'is-ssl': true, 'is-loginauth': false
},
data: {
database: { driver: 'mysql', source: '', host: '127.0.0.1', port: '3306', user: '', password: '******', name: '', config: '', path: '' },
redis: { network: 'tcp', addr: '', read_timeout: '200ms', write_timeout: '200ms' }
}
})
const storageTypes = [
{ label: '本地存储', value: 'local' }, { label: '七牛云', value: 'qiniu' },
{ label: '阿里云 OSS', value: 'aliyun-oss' }, { label: '华为云 OBS', value: 'huawei-obs' },
{ label: '腾讯云 COS', value: 'tencent-cos' }, { label: 'AWS S3', value: 'aws-s3' },
{ label: 'Cloudflare R2', value: 'cloudflare-r2' }, { label: 'MinIO', value: 'minio' }
]
const storageKeys = { 'aliyun-oss': 'aliyun_oss', 'huawei-obs': 'huawei_obs', 'tencent-cos': 'tencent_cos', 'aws-s3': 'aws_s3', 'cloudflare-r2': 'cloudflare_r2', minio: 'minio' }
const currentObjectStorage = computed(() => config.value.admin.storage[storageKeys[config.value.admin.storage.type]])
const initForm = async () => {
const res = await getSystemConfig()
if (res.code === 0) {
config.value = {
admin: { ...config.value.admin, ...res.data.config.admin,
jwt: { ...config.value.admin.jwt, ...res.data.config.admin?.jwt },
captcha: { ...config.value.admin.captcha, ...res.data.config.admin?.captcha },
local: { ...config.value.admin.local, ...res.data.config.admin?.local }
, media: { ...config.value.admin.media, ...res.data.config.admin?.media }
, system: { ...config.value.admin.system, ...res.data.config.admin?.system }
, storage: { ...config.value.admin.storage, ...res.data.config.admin?.storage,
qiniu: { ...config.value.admin.storage.qiniu, ...res.data.config.admin?.storage?.qiniu },
aliyun_oss: { ...config.value.admin.storage.aliyun_oss, ...res.data.config.admin?.storage?.aliyun_oss },
huawei_obs: { ...config.value.admin.storage.huawei_obs, ...res.data.config.admin?.storage?.huawei_obs },
tencent_cos: { ...config.value.admin.storage.tencent_cos, ...res.data.config.admin?.storage?.tencent_cos },
aws_s3: { ...config.value.admin.storage.aws_s3, ...res.data.config.admin?.storage?.aws_s3 },
cloudflare_r2: { ...config.value.admin.storage.cloudflare_r2, ...res.data.config.admin?.storage?.cloudflare_r2 },
minio: { ...config.value.admin.storage.minio, ...res.data.config.admin?.storage?.minio }
}
},
email: { ...config.value.email, ...res.data.config.email },
data: {
database: { ...config.value.data.database, ...res.data.config.data?.database },
redis: { ...config.value.data.redis, ...res.data.config.data?.redis }
}
}
}
}
const update = async () => {
saving.value = true
try {
const res = await setSystemConfig({ config: config.value })
if (res.code === 0) {
ElMessage.success('配置保存成功')
await initForm()
}
} finally {
saving.value = false
}
}
const reload = async () => {
await ElMessageBox.confirm('确定要重载服务配置吗?', '提示')
const res = await reloadSystem()
if (res.code === 0) ElMessage.success('配置已重载')
}
const testEmail = async () => {
testing.value = true
try {
const res = await emailTest()
if (res.code === 0) ElMessage.success('测试邮件发送成功')
} finally {
testing.value = false
}
}
initForm()
</script>
<style scoped>
.config-layout { min-height: 520px; }
</style>