Files
medical-mall/pages/mall/admin/system-settings.uvue

1078 lines
30 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<AdminLayout current-page="system">
<view class="system-settings">
<!-- 页面标题 -->
<view class="page-header">
<text class="page-title">系统设置</text>
<text class="page-subtitle">管理系统基础配置和参数</text>
</view>
<!-- Tab 切换栏 -->
<view class="tab-bar">
<view
v-for="tab in tabs"
:key="tab.key"
class="tab-item"
:class="{ 'active': activeTab === tab.key }"
@click="switchTab(tab.key)"
>
<text class="iconfont tab-icon">{{ tab.icon }}</text>
<text class="tab-title">{{ tab.title }}</text>
</view>
</view>
<!-- 设置分类导航 -->
<view class="settings-nav">
<view
v-for="category in settingCategories"
:key="category.id"
class="nav-item"
:class="{ 'active': activeCategory === category.id }"
@click="setActiveCategory(category.id)"
>
<text class="nav-icon">{{ category.icon }}</text>
<text class="nav-text">{{ category.name }}</text>
</view>
</view>
<!-- 设置内容区域 -->
<view class="settings-content">
<!-- 基本设置 -->
<view v-if="activeCategory === 'basic'" class="setting-section">
<view class="section-header">
<text class="section-title">基本设置</text>
<text class="section-desc">网站基本信息和显示设置</text>
</view>
<view class="setting-items">
<view class="setting-item">
<view class="item-info">
<text class="item-title">网站名称</text>
<text class="item-desc">显示在网站标题和页面的网站名称</text>
</view>
<view class="item-control">
<input
v-model="settings.siteName"
class="setting-input"
placeholder="请输入网站名称"
/>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">网站描述</text>
<text class="item-desc">网站简介用于SEO优化</text>
</view>
<view class="item-control">
<textarea
v-model="settings.siteDescription"
class="setting-textarea"
placeholder="请输入网站描述"
:maxlength="200"
/>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">网站Logo</text>
<text class="item-desc">网站Logo图片建议尺寸200x60px</text>
</view>
<view class="item-control">
<view class="image-upload">
<view class="upload-area" @click="handleLogoUpload">
<text class="iconfont icon-upload"></text>
<text class="upload-text">点击上传Logo</text>
</view>
<view v-if="settings.siteLogo" class="image-preview">
<image :src="settings.siteLogo" class="preview-image" />
</view>
</view>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">网站状态</text>
<text class="item-desc">控制网站是否开放访问</text>
</view>
<view class="item-control">
<view class="switch-wrapper">
<switch
:checked="settings.siteStatus"
@change="handleSiteStatusChange"
color="#1890ff"
/>
<text class="switch-text">{{ settings.siteStatus ? '开放' : '维护中' }}</text>
</view>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">维护提示信息</text>
<text class="item-desc">网站维护时显示的提示信息</text>
</view>
<view class="item-control">
<textarea
v-model="settings.maintenanceMessage"
class="setting-textarea"
placeholder="请输入维护提示信息"
:disabled="!settings.siteStatus"
/>
</view>
</view>
</view>
</view>
<!-- 安全设置 -->
<view v-else-if="activeCategory === 'security'" class="setting-section">
<view class="section-header">
<text class="section-title">安全设置</text>
<text class="section-desc">账号安全和登录相关配置</text>
</view>
<view class="setting-items">
<view class="setting-item">
<view class="item-info">
<text class="item-title">登录失败锁定</text>
<text class="item-desc">连续登录失败后锁定账号</text>
</view>
<view class="item-control">
<view class="switch-wrapper">
<switch
:checked="settings.loginLockEnabled"
@change="settings.loginLockEnabled = $event.detail.value"
color="#1890ff"
/>
<text class="switch-text">{{ settings.loginLockEnabled ? '启用' : '禁用' }}</text>
</view>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">允许失败次数</text>
<text class="item-desc">连续登录失败的最大次数</text>
</view>
<view class="item-control">
<input
v-model="settings.maxLoginAttempts"
class="setting-input small"
type="number"
:disabled="!settings.loginLockEnabled"
min="3"
max="10"
/>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">锁定时间(分钟)</text>
<text class="item-desc">账号锁定的持续时间</text>
</view>
<view class="item-control">
<input
v-model="settings.lockDuration"
class="setting-input small"
type="number"
:disabled="!settings.loginLockEnabled"
min="5"
max="1440"
/>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">密码复杂度要求</text>
<text class="item-desc">用户密码必须包含的字符类型</text>
</view>
<view class="item-control">
<view class="checkbox-group">
<view class="checkbox-item">
<checkbox
:checked="settings.passwordRequireUppercase"
@change="settings.passwordRequireUppercase = $event.detail.value"
/>
<text>大写字母</text>
</view>
<view class="checkbox-item">
<checkbox
:checked="settings.passwordRequireLowercase"
@change="settings.passwordRequireLowercase = $event.detail.value"
/>
<text>小写字母</text>
</view>
<view class="checkbox-item">
<checkbox
:checked="settings.passwordRequireNumbers"
@change="settings.passwordRequireNumbers = $event.detail.value"
/>
<text>数字</text>
</view>
<view class="checkbox-item">
<checkbox
:checked="settings.passwordRequireSymbols"
@change="settings.passwordRequireSymbols = $event.detail.value"
/>
<text>特殊字符</text>
</view>
</view>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">密码最小长度</text>
<text class="item-desc">密码的最小字符长度</text>
</view>
<view class="item-control">
<input
v-model="settings.minPasswordLength"
class="setting-input small"
type="number"
min="6"
max="32"
/>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">会话超时时间</text>
<text class="item-desc">用户登录后的会话有效期(分钟)</text>
</view>
<view class="item-control">
<input
v-model="settings.sessionTimeout"
class="setting-input small"
type="number"
min="15"
max="1440"
/>
</view>
</view>
</view>
</view>
<!-- 邮件设置 -->
<view v-else-if="activeCategory === 'email'" class="setting-section">
<view class="section-header">
<text class="section-title">邮件设置</text>
<text class="section-desc">邮件服务器配置和模板设置</text>
</view>
<view class="setting-items">
<view class="setting-item">
<view class="item-info">
<text class="item-title">SMTP服务器</text>
<text class="item-desc">邮件发送服务器地址</text>
</view>
<view class="item-control">
<input
v-model="settings.smtpHost"
class="setting-input"
placeholder="smtp.example.com"
/>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">SMTP端口</text>
<text class="item-desc">邮件服务器端口号</text>
</view>
<view class="item-control">
<input
v-model="settings.smtpPort"
class="setting-input small"
type="number"
placeholder="587"
/>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">发件人邮箱</text>
<text class="item-desc">系统邮件的发件人地址</text>
</view>
<view class="item-control">
<input
v-model="settings.smtpUsername"
class="setting-input"
placeholder="noreply@example.com"
type="email"
/>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">授权密码</text>
<text class="item-desc">邮箱授权密码或应用密码</text>
</view>
<view class="item-control">
<input
v-model="settings.smtpPassword"
class="setting-input"
placeholder="请输入授权密码"
type="password"
/>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">启用SSL</text>
<text class="item-desc">是否启用SSL加密连接</text>
</view>
<view class="item-control">
<view class="switch-wrapper">
<switch
:checked="settings.smtpSSL"
@change="settings.smtpSSL = $event.detail.value"
color="#1890ff"
/>
<text class="switch-text">{{ settings.smtpSSL ? '启用' : '禁用' }}</text>
</view>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">测试邮件</text>
<text class="item-desc">发送测试邮件验证配置是否正确</text>
</view>
<view class="item-control">
<button class="btn-secondary" @click="sendTestEmail">
发送测试邮件
</button>
</view>
</view>
</view>
</view>
<!-- 支付设置 -->
<view v-else-if="activeCategory === 'payment'" class="setting-section">
<view class="section-header">
<text class="section-title">支付设置</text>
<text class="section-desc">第三方支付平台配置</text>
</view>
<view class="setting-items">
<view class="setting-item">
<view class="item-info">
<text class="item-title">支付宝支付</text>
<text class="item-desc">启用支付宝在线支付</text>
</view>
<view class="item-control">
<view class="switch-wrapper">
<switch
:checked="settings.alipayEnabled"
@change="settings.alipayEnabled = $event.detail.value"
color="#1890ff"
/>
<text class="switch-text">{{ settings.alipayEnabled ? '启用' : '禁用' }}</text>
</view>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">支付宝应用ID</text>
<text class="item-desc">支付宝开放平台的应用ID</text>
</view>
<view class="item-control">
<input
v-model="settings.alipayAppId"
class="setting-input"
placeholder="请输入应用ID"
:disabled="!settings.alipayEnabled"
/>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">微信支付</text>
<text class="item-desc">启用微信支付</text>
</view>
<view class="item-control">
<view class="switch-wrapper">
<switch
:checked="settings.wechatPayEnabled"
@change="settings.wechatPayEnabled = $event.detail.value"
color="#1890ff"
/>
<text class="switch-text">{{ settings.wechatPayEnabled ? '启用' : '禁用' }}</text>
</view>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">微信商户号</text>
<text class="item-desc">微信支付商户号</text>
</view>
<view class="item-control">
<input
v-model="settings.wechatMerchantId"
class="setting-input"
placeholder="请输入商户号"
:disabled="!settings.wechatPayEnabled"
/>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">货币单位</text>
<text class="item-desc">系统使用的货币单位</text>
</view>
<view class="item-control">
<picker
mode="selector"
:range="currencyOptions"
:value="settings.currency"
@change="settings.currency = $event.detail.value"
>
<view class="setting-select">
<text>{{ currencyOptions[settings.currency] }}</text>
<text class="iconfont icon-down"></text>
</view>
</picker>
</view>
</view>
</view>
</view>
<!-- 其他设置 -->
<view v-else-if="activeCategory === 'other'" class="setting-section">
<view class="section-header">
<text class="section-title">其他设置</text>
<text class="section-desc">其他系统配置选项</text>
</view>
<view class="setting-items">
<view class="setting-item">
<view class="item-info">
<text class="item-title">数据备份</text>
<text class="item-desc">定期自动备份系统数据</text>
</view>
<view class="item-control">
<view class="switch-wrapper">
<switch
:checked="settings.autoBackupEnabled"
@change="settings.autoBackupEnabled = $event.detail.value"
color="#1890ff"
/>
<text class="switch-text">{{ settings.autoBackupEnabled ? '启用' : '禁用' }}</text>
</view>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">备份频率</text>
<text class="item-desc">自动备份的时间间隔</text>
</view>
<view class="item-control">
<picker
mode="selector"
:range="backupFrequencyOptions"
:value="settings.backupFrequency"
@change="settings.backupFrequency = $event.detail.value"
:disabled="!settings.autoBackupEnabled"
>
<view class="setting-select">
<text>{{ backupFrequencyOptions[settings.backupFrequency] }}</text>
<text class="iconfont icon-down"></text>
</view>
</picker>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">系统日志</text>
<text class="item-desc">启用详细的系统操作日志</text>
</view>
<view class="item-control">
<view class="switch-wrapper">
<switch
:checked="settings.systemLoggingEnabled"
@change="settings.systemLoggingEnabled = $event.detail.value"
color="#1890ff"
/>
<text class="switch-text">{{ settings.systemLoggingEnabled ? '启用' : '禁用' }}</text>
</view>
</view>
</view>
<view class="setting-item">
<view class="item-info">
<text class="item-title">API限流</text>
<text class="item-desc">限制API请求频率防止滥用</text>
</view>
<view class="item-control">
<view class="switch-wrapper">
<switch
:checked="settings.apiRateLimitingEnabled"
@change="settings.apiRateLimitingEnabled = $event.detail.value"
color="#1890ff"
/>
<text class="switch-text">{{ settings.apiRateLimitingEnabled ? '启用' : '禁用' }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 保存设置 -->
<view class="settings-footer">
<view class="action-buttons">
<button class="btn-secondary" @click="resetSettings">
重置设置
</button>
<button class="btn-primary" @click="saveSettings">
保存设置
</button>
</view>
</view>
</view>
</AdminLayout>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import AdminLayout from '@/layouts/admin/index.uvue'
// Tab 相关
const activeTab = ref('basic')
const tabs = ref([
{ key: 'basic', title: '基本设置', icon: 'icon-basic' },
{ key: 'security', title: '安全设置', icon: 'icon-security' },
{ key: 'email', title: '邮件设置', icon: 'icon-email' }
])
// 设置分类
const settingCategories = [
{ id: 'basic', name: '基本设置', icon: '🏠' },
{ id: 'security', name: '安全设置', icon: '🔒' },
{ id: 'email', name: '邮件设置', icon: '📧' },
{ id: 'payment', name: '支付设置', icon: '💳' },
{ id: 'other', name: '其他设置', icon: '⚙️' }
]
// 响应式数据
const activeCategory = ref('basic')
// 系统设置
const settings = ref({
// 基本设置
siteName: 'Mall电商平台',
siteDescription: '专业的多角色电商解决方案',
siteLogo: '',
siteStatus: true,
maintenanceMessage: '系统正在维护中,请稍后访问',
// 安全设置
loginLockEnabled: true,
maxLoginAttempts: 5,
lockDuration: 30,
passwordRequireUppercase: true,
passwordRequireLowercase: true,
passwordRequireNumbers: true,
passwordRequireSymbols: false,
minPasswordLength: 8,
sessionTimeout: 120,
// 邮件设置
smtpHost: 'smtp.qq.com',
smtpPort: 587,
smtpUsername: '',
smtpPassword: '',
smtpSSL: true,
// 支付设置
alipayEnabled: true,
alipayAppId: '',
wechatPayEnabled: true,
wechatMerchantId: '',
currency: 0,
// 其他设置
autoBackupEnabled: true,
backupFrequency: 0,
systemLoggingEnabled: true,
apiRateLimitingEnabled: true
})
// 选项数据
const currencyOptions = ['人民币(CNY)', '美元(USD)', '欧元(EUR)']
const backupFrequencyOptions = ['每日', '每周', '每月']
// 方法
const setActiveCategory = (categoryId: string) => {
activeCategory.value = categoryId
}
const handleSiteStatusChange = (e: any) => {
settings.value.siteStatus = e.detail.value
}
const handleLogoUpload = () => {
uni.showToast({
title: 'Logo上传功能开发中',
icon: 'none'
})
}
const sendTestEmail = () => {
if (!settings.value.smtpHost || !settings.value.smtpUsername) {
uni.showToast({
title: '请先配置邮件服务器信息',
icon: 'none'
})
return
}
uni.showToast({
title: '发送测试邮件...',
icon: 'loading',
duration: 2000
})
// TODO: 实现发送测试邮件逻辑
setTimeout(() => {
uni.showToast({
title: '测试邮件发送成功',
icon: 'success'
})
}, 2000)
}
const resetSettings = () => {
uni.showModal({
title: '确认重置',
content: '确定要重置所有设置为默认值吗?',
success: (res) => {
if (res.confirm) {
// TODO: 实现重置设置逻辑
uni.showToast({
title: '设置已重置',
icon: 'success'
})
}
}
})
}
const saveSettings = () => {
// 表单验证
if (!settings.value.siteName.trim()) {
uni.showToast({
title: '请输入网站名称',
icon: 'none'
})
return
}
// TODO: 实现保存设置逻辑
console.log('保存设置:', settings.value)
uni.showToast({
title: '设置保存成功',
icon: 'success'
})
}
// 生命周期
// Tab 切换方法
const switchTab = (tabKey: string) => {
activeTab.value = tabKey
}
// 页面生命周期
onLoad((options: any) => {
// 处理页面参数切换到对应的tab
if (options && options.tab) {
if (['basic', 'security', 'email'].includes(options.tab)) {
activeTab.value = options.tab
} else {
activeTab.value = 'basic'
}
} else {
activeTab.value = 'basic'
}
console.log('系统设置页面加载,参数:', options)
})
onMounted(() => {
// 初始化设置数据
console.log('系统设置页面初始化')
})
</script>
<style lang="scss">
/* Tab 栏样式 */
.tab-bar {
display: flex;
background-color: #ffffff;
border-radius: 8rpx;
padding: 8rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
}
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
padding: 16rpx 24rpx;
border-radius: 6rpx;
cursor: pointer;
transition: all 0.2s;
background-color: #f5f5f5;
color: #666666;
}
.tab-item:hover {
background-color: #e8e8e8;
}
.tab-item.active {
background-color: #1890ff;
color: #ffffff;
}
.tab-icon {
font-size: 16rpx;
}
.tab-title {
font-size: 14rpx;
font-weight: 500;
}
.system-settings {
padding: 20rpx;
}
.page-header {
margin-bottom: 30rpx;
}
.page-title {
display: block;
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.page-subtitle {
display: block;
font-size: 26rpx;
color: #666;
}
.settings-nav {
display: flex;
background-color: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
overflow-x: auto;
}
.nav-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx 30rpx;
margin-right: 20rpx;
border-radius: 8rpx;
cursor: pointer;
transition: all 0.3s;
min-width: 120rpx;
}
.nav-item:last-child {
margin-right: 0;
}
.nav-item.active {
background-color: #1890ff;
color: #fff;
}
.nav-item:not(.active):hover {
background-color: #f5f5f5;
}
.nav-icon {
font-size: 32rpx;
margin-bottom: 8rpx;
}
.nav-text {
font-size: 24rpx;
font-weight: 500;
}
.settings-content {
background-color: #fff;
border-radius: 12rpx;
padding: 30rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.setting-section {
min-height: 400rpx;
}
.section-header {
margin-bottom: 30rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #eee;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
display: block;
}
.section-desc {
font-size: 26rpx;
color: #666;
}
.setting-items {
display: flex;
flex-direction: column;
gap: 30rpx;
}
.setting-item {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.setting-item:last-child {
border-bottom: none;
}
.item-info {
flex: 2;
margin-right: 30rpx;
}
.item-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 6rpx;
display: block;
}
.item-desc {
font-size: 24rpx;
color: #666;
line-height: 1.4;
}
.item-control {
flex: 1;
min-width: 200rpx;
}
.setting-input {
width: 100%;
padding: 16rpx 20rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.setting-input.small {
width: 150rpx;
}
.setting-textarea {
width: 100%;
min-height: 80rpx;
padding: 16rpx 20rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
font-size: 28rpx;
box-sizing: border-box;
resize: vertical;
}
.setting-select {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx 20rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
background-color: #fff;
cursor: pointer;
font-size: 28rpx;
}
.switch-wrapper {
display: flex;
align-items: center;
gap: 15rpx;
}
.switch-text {
font-size: 26rpx;
color: #666;
}
.checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.checkbox-item {
display: flex;
align-items: center;
gap: 8rpx;
font-size: 26rpx;
color: #666;
}
.image-upload {
display: flex;
align-items: center;
gap: 20rpx;
}
.upload-area {
width: 120rpx;
height: 60rpx;
border: 2rpx dashed #ddd;
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #999;
font-size: 24rpx;
}
.upload-text {
font-size: 22rpx;
margin-left: 8rpx;
}
.preview-image {
width: 120rpx;
height: 60rpx;
border-radius: 8rpx;
object-fit: cover;
}
.settings-footer {
margin-top: 40rpx;
text-align: center;
}
.action-buttons {
display: flex;
gap: 30rpx;
justify-content: center;
}
.btn-primary {
background-color: #1890ff;
color: #fff;
border: none;
border-radius: 8rpx;
padding: 16rpx 40rpx;
font-size: 28rpx;
cursor: pointer;
font-weight: 500;
}
.btn-secondary {
background-color: #fff;
color: #666;
border: 1rpx solid #ddd;
border-radius: 8rpx;
padding: 16rpx 40rpx;
font-size: 28rpx;
cursor: pointer;
}
.iconfont {
font-family: 'iconfont';
font-size: 24rpx;
}
/* 响应式设计 */
@media screen and (max-width: 750rpx) {
.settings-nav {
padding: 15rpx;
}
.nav-item {
padding: 15rpx 20rpx;
margin-right: 10rpx;
min-width: 100rpx;
}
.setting-item {
flex-direction: column;
gap: 15rpx;
}
.item-info {
margin-right: 0;
}
.item-control {
min-width: auto;
}
.setting-input.small {
width: 100%;
}
.checkbox-group {
flex-direction: column;
gap: 10rpx;
}
.action-buttons {
flex-direction: column;
gap: 15rpx;
}
.btn-primary,
.btn-secondary {
width: 100%;
}
}
</style>