优化更新

This commit is contained in:
2026-01-23 22:41:10 +08:00
parent 0608f0febb
commit 8327f6c3e6
6 changed files with 170 additions and 302 deletions

View File

@@ -132,7 +132,16 @@ export function useTable(options = {}) {
pagination.total = res.data?.total || 0 pagination.total = res.data?.total || 0
} else { } else {
// 非分页数据(如树形数据) // 非分页数据(如树形数据)
tableData.value = res.data || [] // 确保数据是数组,如果不是数组则包装成数组
const data = res.data
if (Array.isArray(data)) {
tableData.value = data
} else if (data && typeof data === 'object') {
// 如果返回的是对象,可能包含 list 或 items 等字段
tableData.value = data.list || data.items || data.data || []
} else {
tableData.value = []
}
} }
} else { } else {
message.error(res.message || '加载数据失败') message.error(res.message || '加载数据失败')

View File

@@ -96,7 +96,6 @@ const layoutStore = useLayoutStore()
const showTags = ref(true) const showTags = ref(true)
const selectedTag = ref(null) const selectedTag = ref(null)
const visitedViews = computed(() => layoutStore.viewTags) const visitedViews = computed(() => layoutStore.viewTags)
console.log(visitedViews)
// 右键菜单状态 // 右键菜单状态
const contextMenu = ref({ const contextMenu = ref({
visible: false, visible: false,

View File

@@ -18,11 +18,13 @@
<a-form-item label="排序" name="sort"> <a-form-item label="排序" name="sort">
<a-input-number v-model:value="form.sort" :min="1" :step="1" style="width: 100%" placeholder="请输入排序" /> <a-input-number v-model:value="form.sort" :min="1" :step="1" style="width: 100%" placeholder="请输入排序" />
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{ offset: 5 }">
<div style="display: flex; gap: 10px">
<a-button v-if="mode !== 'show'" type="primary" :loading="isSaveing" @click="submit"> </a-button>
<a-button @click="handleCancel"> </a-button>
</div>
</a-form-item>
</a-form> </a-form>
<template #footer>
<a-button @click="handleCancel"> </a-button>
<a-button v-if="mode !== 'show'" type="primary" :loading="isSaveing" @click="submit"> </a-button>
</template>
</a-modal> </a-modal>
</template> </template>
@@ -31,6 +33,10 @@ import { ref, reactive } from 'vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import authApi from '@/api/auth' import authApi from '@/api/auth'
defineOptions({
name: 'DepartmentSave'
})
const emit = defineEmits(['success', 'closed']) const emit = defineEmits(['success', 'closed'])
const mode = ref('add') const mode = ref('add')
@@ -68,7 +74,7 @@ const rules = {
const departments = ref([]) const departments = ref([])
const departmentFieldNames = { const departmentFieldNames = {
title: 'title', title: 'title',
key: 'id', value: 'id',
children: 'children' children: 'children'
} }

View File

@@ -28,7 +28,7 @@
<a-button type="primary" @click="handleSearch"> <a-button type="primary" @click="handleSearch">
<template #icon><search-outlined /></template> <template #icon><search-outlined /></template>
</a-button> </a-button>
<a-button @click="handleLogReset"> <a-button @click="handleReset">
<template #icon><redo-outlined /></template> <template #icon><redo-outlined /></template>
</a-button> </a-button>
</a-space> </a-space>

View File

@@ -51,15 +51,12 @@
<script setup> <script setup>
import { ref, reactive, watch } from 'vue' import { ref, reactive, watch } from 'vue'
import { useI18n } from 'vue-i18n'
// 定义组件名称 // 定义组件名称
defineOptions({ defineOptions({
name: 'ConfigModal', name: 'ConfigModal',
}) })
const { t } = useI18n()
const props = defineProps({ const props = defineProps({
visible: { visible: {
type: Boolean, type: Boolean,
@@ -120,7 +117,7 @@ const handleConfirm = async () => {
try { try {
const values = await formRef.value.validate() const values = await formRef.value.validate()
emit('confirm', values) emit('confirm', values)
} catch (error) { } catch {
// 表单验证错误,不处理 // 表单验证错误,不处理
} }
} }

View File

@@ -1,192 +1,127 @@
<template> <template>
<div class="pages system-setting"> <div class="pages system-setting">
<!-- 头部 -->
<div class="page-header">
<div class="header-left">
<a-typography-title :level="4" class="page-title">
<SettingOutlined />
系统设置
</a-typography-title>
<a-typography-text type="secondary" class="page-subtitle">
管理系统各项配置参数
</a-typography-text>
</div>
<div class="header-right">
<a-button type="primary" @click="handleAddConfig">
<template #icon><PlusOutlined /></template>
新增配置
</a-button>
</div>
</div>
<!-- 主要内容区域 --> <!-- 主要内容区域 -->
<div class="page-content"> <div class="page-content">
<a-card :bordered="false" class="setting-card"> <a-tabs v-model:activeKey="activeTab" @change="handleTabChange" class="setting-tabs">
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange" class="setting-tabs"> <template #rightExtra>
<a-tab-pane v-for="category in categories" :key="category.name" :tab="category.title"> <a-button type="primary" @click="handleAddConfig">
<a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }" class="setting-form"> <template #icon>
<a-form-item <PlusOutlined />
v-for="field in fields.filter(f => f.category === category.name)" </template>
:key="field.name" 新增配置
:label="field.title" </a-button>
:required="field.required" </template>
> <a-tab-pane v-for="category in categories" :key="category.name" :tab="category.title">
<div class="form-item-content"> <a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }" class="setting-form">
<div class="form-input-wrapper"> <a-form-item v-for="field in fields.filter(f => f.category === category.name)"
<!-- 文本输入 --> :key="field.name" :label="field.title" :required="field.required">
<a-input <div class="form-item-content">
v-if="field.type === 'text'" <div class="form-input-wrapper">
v-model:value="formData[field.name]" <!-- 文本输入 -->
:placeholder="field.placeholder || '请输入'" <a-input v-if="field.type === 'text'" v-model:value="formData[field.name]"
allow-clear :placeholder="field.placeholder || '请输入'" allow-clear />
/> <!-- 文本域 -->
<!-- 文本域 --> <a-textarea v-else-if="field.type === 'textarea'"
<a-textarea v-model:value="formData[field.name]"
v-else-if="field.type === 'textarea'" :placeholder="field.placeholder || '请输入'" :rows="4"
v-model:value="formData[field.name]" :maxlength="field.maxLength" :show-count="field.maxLength > 0"
:placeholder="field.placeholder || '请输入'" allow-clear />
:rows="4" <!-- 数字输入 -->
:maxlength="field.maxLength" <a-input-number v-else-if="field.type === 'number'"
:show-count="field.maxLength > 0" v-model:value="formData[field.name]"
allow-clear :placeholder="field.placeholder || '请输入'" :min="field.min" :max="field.max"
/> :precision="field.precision || 0" style="width: 100%" />
<!-- 数字输入 --> <!-- 开关 -->
<a-input-number <a-switch v-else-if="field.type === 'switch'"
v-else-if="field.type === 'number'" v-model:checked="formData[field.name]" :checked-children="启用"
v-model:value="formData[field.name]" :un-checked-children="禁用" />
:placeholder="field.placeholder || '请输入'" <!-- 下拉选择 -->
:min="field.min" <a-select v-else-if="field.type === 'select'"
:max="field.max" v-model:value="formData[field.name]"
:precision="field.precision || 0" :placeholder="field.placeholder || '请选择'" style="width: 100%" allow-clear>
style="width: 100%" <a-select-option v-for="option in field.options" :key="option.value"
/> :value="option.value">
<!-- 开关 --> {{ option.label }}
<a-switch </a-select-option>
v-else-if="field.type === 'switch'" </a-select>
v-model:checked="formData[field.name]" <!-- 多选 -->
:checked-children="启用" <a-select v-else-if="field.type === 'multiselect'"
:un-checked-children="禁用" v-model:value="formData[field.name]"
/> :placeholder="field.placeholder || '请选择'" mode="multiple"
<!-- 下拉选择 --> style="width: 100%" allow-clear>
<a-select <a-select-option v-for="option in field.options" :key="option.value"
v-else-if="field.type === 'select'" :value="option.value">
v-model:value="formData[field.name]" {{ option.label }}
:placeholder="field.placeholder || '请选择'" </a-select-option>
style="width: 100%" </a-select>
allow-clear <!-- 日期时间 -->
> <a-date-picker v-else-if="field.type === 'datetime'"
<a-select-option v-model:value="formData[field.name]"
v-for="option in field.options" :placeholder="field.placeholder || '请选择'" style="width: 100%" show-time
:key="option.value" format="YYYY-MM-DD HH:mm:ss" allow-clear />
:value="option.value" <!-- 颜色选择器 -->
> <div v-else-if="field.type === 'color'" class="color-picker-wrapper">
{{ option.label }} <div class="color-preview"
</a-select-option> :style="{ backgroundColor: formData[field.name] }"></div>
</a-select> <a-input v-model:value="formData[field.name]"
<!-- 多选 --> placeholder="请输入颜色值#ff0000" allow-clear class="color-text" />
<a-select
v-else-if="field.type === 'multiselect'"
v-model:value="formData[field.name]"
:placeholder="field.placeholder || '请选择'"
mode="multiple"
style="width: 100%"
allow-clear
>
<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 || '请选择'"
style="width: 100%"
show-time
format="YYYY-MM-DD HH:mm:ss"
allow-clear
/>
<!-- 颜色选择器 -->
<div v-else-if="field.type === 'color'" class="color-picker-wrapper">
<div class="color-preview" :style="{ backgroundColor: formData[field.name] }"></div>
<a-input
v-model:value="formData[field.name]"
placeholder="请输入颜色值#ff0000"
allow-clear
class="color-text"
/>
</div>
<!-- 图片上传 -->
<div v-else-if="field.type === 'image'" class="image-uploader-wrapper">
<sc-upload
v-model="formData[field.name]"
:max-count="1"
:tip="field.tip"
upload-text="上传图片"
/>
</div>
<!-- 默认文本输入 -->
<a-input
v-else
v-model:value="formData[field.name]"
:placeholder="field.placeholder || '请输入'"
allow-clear
/>
</div> </div>
<div class="form-actions"> <!-- 图片上传 -->
<a-tooltip title="编辑配置项"> <div v-else-if="field.type === 'image'" class="image-uploader-wrapper">
<EditOutlined class="action-icon edit-icon" @click="handleEditField(field)" /> <sc-upload v-model="formData[field.name]" :max-count="1" :tip="field.tip"
</a-tooltip> upload-text="上传图片" />
</div> </div>
<!-- 默认文本输入 -->
<a-input v-else v-model:value="formData[field.name]"
:placeholder="field.placeholder || '请输入'" allow-clear />
</div> </div>
<div v-if="field.tip" class="field-tip"> <div class="form-actions">
<InfoCircleOutlined class="tip-icon" /> <a-tooltip title="编辑配置项">
{{ field.tip }} <EditOutlined class="action-icon edit-icon"
@click="handleEditField(field)" />
</a-tooltip>
</div> </div>
</a-form-item> </div>
</a-form> <div v-if="field.tip" class="field-tip">
<InfoCircleOutlined class="tip-icon" />
{{ field.tip }}
</div>
</a-form-item>
<a-form-item :wrapper-col="{ offset: 4, span: 16 }">
<div style="display: flex; gap: 10px;">
<a-button type="primary" size="large" :loading="saving" @click="handleSave">
<template #icon>
<SaveOutlined />
</template>
保存配置
</a-button>
<a-button size="large" @click="handleReset">
<template #icon>
<RedoOutlined />
</template>
重置
</a-button>
</div>
</a-form-item>
</a-form>
<!-- 空状态 --> <!-- 空状态 -->
<a-empty <a-empty v-if="fields.filter(f => f.category === category.name).length === 0"
v-if="fields.filter(f => f.category === category.name).length === 0" description="暂无配置项">
description="暂无配置项" <a-button type="primary" @click="handleAddConfig">
> <template #icon>
<a-button type="primary" @click="handleAddConfig"> <PlusOutlined />
<template #icon><PlusOutlined /></template> </template>
添加配置 添加配置
</a-button> </a-button>
</a-empty> </a-empty>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</a-card>
</div>
<!-- 底部操作栏 -->
<div class="page-footer">
<a-space :size="16">
<a-button size="large" @click="handleReset">
<template #icon><RedoOutlined /></template>
重置
</a-button>
<a-button type="primary" size="large" :loading="saving" @click="handleSave">
<template #icon><SaveOutlined /></template>
保存配置
</a-button>
</a-space>
</div> </div>
<!-- 配置弹窗 --> <!-- 配置弹窗 -->
<ConfigModal <ConfigModal v-model:visible="modalVisible" :is-edit="isEditMode" :categories="categories" :initial-data="currentEditData" @confirm="handleModalConfirm" />
v-model:visible="modalVisible"
:is-edit="isEditMode"
:categories="categories"
:initial-data="currentEditData"
@confirm="handleModalConfirm"
/>
</div> </div>
</template> </template>
@@ -465,95 +400,53 @@ onMounted(() => {
overflow: hidden; overflow: hidden;
background: #f5f5f5; background: #f5f5f5;
.page-header {
background: #fff;
padding: 20px 24px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #f0f0f0;
.header-left {
display: flex;
flex-direction: column;
gap: 4px;
.page-title {
margin: 0;
font-size: 18px;
font-weight: 500;
color: #262626;
display: flex;
align-items: center;
gap: 8px;
:deep(.anticon) {
color: #1890ff;
}
}
.page-subtitle {
font-size: 13px;
color: #8c8c8c;
}
}
}
.page-content { .page-content {
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
padding: 16px 24px 0; padding: 16px;
.setting-card { .setting-tabs {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; background: #ffffff;
padding: 0 10px;
border-radius: 10px;
:deep(.ant-card-body) { :deep(.ant-tabs-tab-btn) {
flex: 1; padding: 0 15px;
overflow: hidden;
padding: 24px;
display: flex;
flex-direction: column;
} }
.setting-tabs { :deep(.ant-tabs-nav) {
margin-bottom: 24px;
}
:deep(.ant-tabs-content) {
flex: 1;
overflow-y: auto;
padding-right: 8px;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #d9d9d9;
border-radius: 3px;
&:hover {
background: #bfbfbf;
}
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
:deep(.ant-tabs-tabpane) {
height: 100%; height: 100%;
display: flex; overflow-y: auto;
flex-direction: column;
:deep(.ant-tabs-nav) {
margin-bottom: 24px;
}
:deep(.ant-tabs-content) {
flex: 1;
overflow-y: auto;
padding-right: 8px;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #d9d9d9;
border-radius: 3px;
&:hover {
background: #bfbfbf;
}
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
:deep(.ant-tabs-tabpane) {
height: 100%;
overflow-y: auto;
}
} }
} }
@@ -628,41 +521,5 @@ onMounted(() => {
} }
} }
} }
.page-footer {
background: #fff;
padding: 16px 24px;
border-top: 1px solid #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
}
}
:deep(.ant-form-item) {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
.ant-form-item-label {
font-weight: 500;
color: #262626;
> label {
height: 32px;
line-height: 32px;
}
&.ant-form-item-required::before {
color: #ff4d4f;
}
}
}
:deep(.ant-tabs-tab) {
font-size: 14px;
font-weight: 500;
} }
</style> </style>