更新
This commit is contained in:
@@ -1,314 +1,338 @@
|
||||
<template>
|
||||
<div class="upload-demo">
|
||||
<a-card title="图片上传组件示例" class="demo-card">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-card type="inner" title="单图上传">
|
||||
<ImageUpload v-model="singleImage" />
|
||||
<div class="result">
|
||||
<strong>结果:</strong>
|
||||
<p>{{ singleImage || '暂无图片' }}</p>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card type="inner" title="多图上传(最多5张)">
|
||||
<ImageUpload
|
||||
v-model="multipleImages"
|
||||
:max-count="5"
|
||||
@change="handleImageChange"
|
||||
/>
|
||||
<div class="result">
|
||||
<strong>结果:</strong>
|
||||
<p v-if="multipleImages.length > 0">
|
||||
{{ multipleImages.join(', ') }}
|
||||
</p>
|
||||
<p v-else>暂无图片</p>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="system-setting">
|
||||
<a-card :bordered="false">
|
||||
<template #title>
|
||||
<div class="page-title">
|
||||
<SettingOutlined />
|
||||
<span>{{ $t('common.systemSettings') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Tab 页签 -->
|
||||
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange">
|
||||
<template #rightExtra>
|
||||
<a-button type="primary" @click="handleAddConfig">
|
||||
<PlusOutlined />
|
||||
{{ $t('common.addConfig') }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<a-tab-pane v-for="category in categories" :key="category.name" :tab="category.title">
|
||||
<a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }" class="setting-form">
|
||||
<a-form-item v-for="field in fields.filter(f => f.category === category.name)" :key="field.name"
|
||||
:label="field.title">
|
||||
<div class="form-item-content">
|
||||
<div class="form-input-wrapper">
|
||||
<!-- 文本输入 -->
|
||||
<a-input v-if="field.type === 'text'" v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseEnter')" />
|
||||
<!-- 文本域 -->
|
||||
<a-textarea v-else-if="field.type === 'textarea'"
|
||||
v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseEnter')" :rows="4" />
|
||||
<!-- 数字输入 -->
|
||||
<a-input-number v-else-if="field.type === 'number'"
|
||||
v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseEnter')"
|
||||
style="width: 100%" />
|
||||
<!-- 开关 -->
|
||||
<a-switch v-else-if="field.type === 'switch'"
|
||||
v-model:checked="formData[field.name]" />
|
||||
<!-- 下拉选择 -->
|
||||
<a-select v-else-if="field.type === 'select'" v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseSelect')"
|
||||
style="width: 100%">
|
||||
<a-select-option v-for="option in field.options" :key="option.value"
|
||||
:value="option.value">
|
||||
{{ option.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<!-- 多选 -->
|
||||
<a-select v-else-if="field.type === 'multiselect'"
|
||||
v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseSelect')" mode="multiple"
|
||||
style="width: 100%">
|
||||
<a-select-option v-for="option in field.options" :key="option.value"
|
||||
:value="option.value">
|
||||
{{ option.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<!-- 日期时间 -->
|
||||
<a-date-picker v-else-if="field.type === 'datetime'"
|
||||
v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseSelect')"
|
||||
style="width: 100%" show-time format="YYYY-MM-DD HH:mm:ss" />
|
||||
<!-- 颜色选择器 -->
|
||||
<a-input v-else-if="field.type === 'color'" v-model:value="formData[field.name]"
|
||||
type="color" style="width: 100px" />
|
||||
<!-- 默认文本输入 -->
|
||||
<a-input v-else v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseEnter')" />
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<EditOutlined class="action-icon edit-icon" :title="$t('common.edit')"
|
||||
@click="handleEditField(field)" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="field.tip" class="field-tip">{{ field.tip }}</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<a-empty v-if="fields.filter(f => f.category === category.name).length === 0"
|
||||
:description="$t('common.noConfig')" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<!-- 底部保存按钮 -->
|
||||
<div class="save-actions">
|
||||
<a-space>
|
||||
<a-button @click="handleReset">
|
||||
{{ $t('common.reset') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="saving" @click="handleSave">
|
||||
<SaveOutlined />
|
||||
{{ $t('common.save') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card title="图片尺寸限制示例" class="demo-card">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-card type="inner" title="限制图片尺寸 800x600">
|
||||
<ImageUpload
|
||||
v-model="sizeImage"
|
||||
:min-width="800"
|
||||
:max-width="1920"
|
||||
:min-height="600"
|
||||
:max-height="1080"
|
||||
tip="尺寸要求:800x600 ~ 1920x1080"
|
||||
/>
|
||||
<div class="result">
|
||||
<strong>结果:</strong>
|
||||
<p>{{ sizeImage || '暂无图片' }}</p>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card type="inner" title="自定义上传文字">
|
||||
<ImageUpload
|
||||
v-model="customImage"
|
||||
upload-text="点击选择图片"
|
||||
tip="支持 JPG、PNG 格式,最大 10MB"
|
||||
/>
|
||||
<div class="result">
|
||||
<strong>结果:</strong>
|
||||
<p>{{ customImage || '暂无图片' }}</p>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<a-card title="上传事件监听示例" class="demo-card">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-card type="inner" title="监听上传事件">
|
||||
<ImageUpload
|
||||
v-model="eventImage"
|
||||
@upload-success="handleUploadSuccess"
|
||||
@upload-error="handleUploadError"
|
||||
@preview="handlePreview"
|
||||
/>
|
||||
<div class="result">
|
||||
<strong>结果:</strong>
|
||||
<p>{{ eventImage || '暂无图片' }}</p>
|
||||
<p v-if="eventLog" class="event-log">
|
||||
<strong>事件日志:</strong>
|
||||
<pre>{{ eventLog }}</pre>
|
||||
</p>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<a-card title="文件上传组件示例" class="demo-card">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-card type="inner" title="单文件上传">
|
||||
<FileUpload v-model="singleFile" />
|
||||
<div class="result">
|
||||
<strong>结果:</strong>
|
||||
<p>{{ singleFile || '暂无文件' }}</p>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card type="inner" title="多文件上传">
|
||||
<FileUpload
|
||||
v-model="multipleFiles"
|
||||
:max-count="10"
|
||||
:multiple="true"
|
||||
@change="handleFileChange"
|
||||
@remove="handleFileRemove"
|
||||
/>
|
||||
<div class="result">
|
||||
<strong>结果:</strong>
|
||||
<p v-if="multipleFiles.length > 0">
|
||||
{{ multipleFiles.join(', ') }}
|
||||
</p>
|
||||
<p v-else>暂无文件</p>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<a-card title="限制文件类型示例" class="demo-card">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-card type="inner" title="仅支持 JPG/PNG 图片">
|
||||
<ImageUpload
|
||||
v-model="jpgImage"
|
||||
accept="image/jpeg,image/png"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card type="inner" title="仅支持 PDF/Word 文档">
|
||||
<FileUpload
|
||||
v-model="documentFile"
|
||||
accept=".pdf,.doc,.docx"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<a-card title="禁用状态示例" class="demo-card">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-card type="inner" title="禁用图片上传">
|
||||
<ImageUpload
|
||||
v-model="disabledImage"
|
||||
:disabled="true"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card type="inner" title="禁用文件上传">
|
||||
<FileUpload
|
||||
v-model="disabledFile"
|
||||
:disabled="true"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<a-card title="返回完整文件列表示例" class="demo-card">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-card type="inner" title="返回完整文件对象">
|
||||
<ImageUpload
|
||||
v-model="fullFileList"
|
||||
:return-url="false"
|
||||
@change="handleFullListChange"
|
||||
/>
|
||||
<div class="result">
|
||||
<strong>完整文件列表:</strong>
|
||||
<pre>{{ JSON.stringify(fullFileList, null, 2) }}</pre>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
<!-- 配置弹窗 -->
|
||||
<ConfigModal v-model:visible="modalVisible" :is-edit="isEditMode" :categories="categories"
|
||||
:initial-data="currentEditData" @confirm="handleModalConfirm" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import ImageUpload from '@/components/scUpload/index.vue'
|
||||
import FileUpload from '@/components/scUpload/file.vue'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { SettingOutlined, PlusOutlined, EditOutlined, SaveOutlined } from '@ant-design/icons-vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import systemApi from '@/api/system'
|
||||
import ConfigModal from './components/ConfigModal.vue'
|
||||
|
||||
// 单图上传
|
||||
const singleImage = ref('')
|
||||
// 定义组件名称
|
||||
defineOptions({
|
||||
name: 'SystemSetting',
|
||||
})
|
||||
|
||||
// 多图上传
|
||||
const multipleImages = ref([])
|
||||
const { t } = useI18n()
|
||||
|
||||
// 单文件上传
|
||||
const singleFile = ref('')
|
||||
const activeTab = ref('basic')
|
||||
const saving = ref(false)
|
||||
|
||||
// 多文件上传
|
||||
const multipleFiles = ref([])
|
||||
// 配置分类
|
||||
const categories = ref([
|
||||
{ name: 'basic', title: '基础设置' },
|
||||
{ name: 'security', title: '安全设置' },
|
||||
{ name: 'upload', title: '上传设置' },
|
||||
{ name: 'email', title: '邮件设置' },
|
||||
{ name: 'sms', title: '短信设置' },
|
||||
])
|
||||
|
||||
// 限制类型
|
||||
const jpgImage = ref('')
|
||||
const documentFile = ref('')
|
||||
// 配置字段
|
||||
const fields = ref([])
|
||||
|
||||
// 禁用状态
|
||||
const disabledImage = ref('')
|
||||
const disabledFile = ref('')
|
||||
// 表单数据
|
||||
const formData = reactive({})
|
||||
|
||||
// 完整文件列表
|
||||
const fullFileList = ref([])
|
||||
// 弹窗相关
|
||||
const modalVisible = ref(false)
|
||||
const isEditMode = ref(false)
|
||||
const currentEditData = ref({})
|
||||
|
||||
// 新增示例
|
||||
const sizeImage = ref('')
|
||||
const customImage = ref('')
|
||||
const eventImage = ref('')
|
||||
const eventLog = ref('')
|
||||
// 获取配置字段
|
||||
const fetchFields = async () => {
|
||||
try {
|
||||
const res = await systemApi.setting.fields.get()
|
||||
if (res.code === 200) {
|
||||
fields.value = res.data.fields || []
|
||||
categories.value = res.data.categories || categories.value
|
||||
|
||||
// 图片变化事件
|
||||
const handleImageChange = (value, fileList) => {
|
||||
console.log('图片URL数组:', value)
|
||||
console.log('完整文件列表:', fileList)
|
||||
// 初始化表单数据
|
||||
fields.value.forEach(field => {
|
||||
formData[field.name] = field.value || ''
|
||||
})
|
||||
|
||||
// 设置第一个 tab 为默认激活
|
||||
if (categories.value.length > 0) {
|
||||
activeTab.value = categories.value[0].name
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(t('common.fetchConfigFailed'))
|
||||
console.error('获取配置字段失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 文件变化事件
|
||||
const handleFileChange = (value, fileList) => {
|
||||
console.log('文件URL数组:', value)
|
||||
console.log('完整文件列表:', fileList)
|
||||
// 切换 Tab
|
||||
const handleTabChange = (key) => {
|
||||
activeTab.value = key
|
||||
}
|
||||
|
||||
// 文件移除事件
|
||||
const handleFileRemove = (file) => {
|
||||
console.log('移除的文件:', file)
|
||||
// 添加配置
|
||||
const handleAddConfig = () => {
|
||||
isEditMode.value = false
|
||||
currentEditData.value = {
|
||||
category: activeTab.value,
|
||||
name: '',
|
||||
title: '',
|
||||
type: 'text',
|
||||
value: '',
|
||||
tip: '',
|
||||
}
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
// 完整文件列表变化事件
|
||||
const handleFullListChange = (value, fileList) => {
|
||||
console.log('完整文件列表:', fileList)
|
||||
// 编辑字段
|
||||
const handleEditField = (field) => {
|
||||
isEditMode.value = true
|
||||
currentEditData.value = {
|
||||
category: field.category,
|
||||
name: field.name,
|
||||
title: field.title,
|
||||
type: field.type,
|
||||
value: formData[field.name],
|
||||
tip: field.tip || '',
|
||||
}
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
// 上传成功事件
|
||||
const handleUploadSuccess = (data, file) => {
|
||||
eventLog.value = `上传成功\n文件名: ${file.name}\n响应数据: ${JSON.stringify(data, null, 2)}`
|
||||
console.log('上传成功:', data, file)
|
||||
// 弹窗确认处理
|
||||
const handleModalConfirm = async (values) => {
|
||||
try {
|
||||
let res
|
||||
if (isEditMode.value) {
|
||||
// 编辑模式
|
||||
res = await systemApi.setting.edit.post({
|
||||
...values,
|
||||
name: currentEditData.value.name,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
message.success(t('common.editSuccess'))
|
||||
modalVisible.value = false
|
||||
|
||||
// 更新表单数据
|
||||
formData[currentEditData.value.name] = values.value
|
||||
|
||||
// 更新字段信息
|
||||
const fieldIndex = fields.value.findIndex(f => f.name === currentEditData.value.name)
|
||||
if (fieldIndex > -1) {
|
||||
fields.value[fieldIndex].title = values.title
|
||||
fields.value[fieldIndex].value = values.value
|
||||
fields.value[fieldIndex].tip = values.tip
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 添加模式
|
||||
res = await systemApi.setting.add.post(values)
|
||||
if (res.code === 200) {
|
||||
message.success(t('common.addSuccess'))
|
||||
modalVisible.value = false
|
||||
// 重新获取配置字段
|
||||
await fetchFields()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.errorFields) {
|
||||
return // 表单验证错误
|
||||
}
|
||||
message.error(isEditMode.value ? t('common.editFailed') : t('common.addFailed'))
|
||||
console.error(isEditMode.value ? '编辑配置失败:' : '添加配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 上传失败事件
|
||||
const handleUploadError = (errorMsg, file) => {
|
||||
eventLog.value = `上传失败\n文件名: ${file.name}\n错误信息: ${errorMsg}`
|
||||
console.log('上传失败:', errorMsg, file)
|
||||
// 保存配置
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
saving.value = true
|
||||
const res = await systemApi.setting.save.post(formData)
|
||||
if (res.code === 200) {
|
||||
message.success(t('common.saveSuccess'))
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(t('common.saveFailed'))
|
||||
console.error('保存配置失败:', error)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 预览事件
|
||||
const handlePreview = (file) => {
|
||||
eventLog.value = `预览图片\n文件名: ${file.name}\n状态: ${file.status}`
|
||||
console.log('预览文件:', file)
|
||||
// 重置配置
|
||||
const handleReset = () => {
|
||||
Object.keys(formData).forEach(key => {
|
||||
const field = fields.value.find(f => f.name === key)
|
||||
if (field) {
|
||||
formData[key] = field.value || ''
|
||||
}
|
||||
})
|
||||
message.info(t('common.resetSuccess'))
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchFields()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.upload-demo {
|
||||
padding: 24px;
|
||||
<style scoped lang="scss">
|
||||
.system-setting {
|
||||
.page-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.setting-form {
|
||||
margin-top: 20px;
|
||||
|
||||
.form-item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.form-input-wrapper {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
.action-icon {
|
||||
font-size: 16px;
|
||||
color: #8c8c8c;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.field-tip {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.save-actions {
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-card {
|
||||
margin-bottom: 24px;
|
||||
:deep(.ant-tabs-tab) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.demo-card :deep(.ant-card-body) {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.demo-card :deep(.ant-card-head-title) {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.result {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.result p {
|
||||
margin: 8px 0 0 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.result pre {
|
||||
margin: 8px 0 0 0;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.event-log {
|
||||
margin-top: 12px !important;
|
||||
padding: 8px !important;
|
||||
background-color: #f0f2f5 !important;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.event-log pre {
|
||||
margin: 8px 0 0 0;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user