优化更新

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
} 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 {
message.error(res.message || '加载数据失败')

View File

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

View File

@@ -18,11 +18,13 @@
<a-form-item label="排序" name="sort">
<a-input-number v-model:value="form.sort" :min="1" :step="1" style="width: 100%" placeholder="请输入排序" />
</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>
<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>
</template>
@@ -31,6 +33,10 @@ import { ref, reactive } from 'vue'
import { message } from 'ant-design-vue'
import authApi from '@/api/auth'
defineOptions({
name: 'DepartmentSave'
})
const emit = defineEmits(['success', 'closed'])
const mode = ref('add')
@@ -68,7 +74,7 @@ const rules = {
const departments = ref([])
const departmentFieldNames = {
title: 'title',
key: 'id',
value: 'id',
children: 'children'
}

View File

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

View File

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

View File

@@ -1,192 +1,127 @@
<template>
<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">
<a-card :bordered="false" class="setting-card">
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange" class="setting-tabs">
<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"
:required="field.required"
>
<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 || '请输入'"
allow-clear
/>
<!-- 文本域 -->
<a-textarea
v-else-if="field.type === 'textarea'"
v-model:value="formData[field.name]"
:placeholder="field.placeholder || '请输入'"
:rows="4"
:maxlength="field.maxLength"
:show-count="field.maxLength > 0"
allow-clear
/>
<!-- 数字输入 -->
<a-input-number
v-else-if="field.type === 'number'"
v-model:value="formData[field.name]"
:placeholder="field.placeholder || '请输入'"
:min="field.min"
:max="field.max"
:precision="field.precision || 0"
style="width: 100%"
/>
<!-- 开关 -->
<a-switch
v-else-if="field.type === 'switch'"
v-model:checked="formData[field.name]"
:checked-children="启用"
:un-checked-children="禁用"
/>
<!-- 下拉选择 -->
<a-select
v-else-if="field.type === 'select'"
v-model:value="formData[field.name]"
:placeholder="field.placeholder || '请选择'"
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-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
/>
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange" class="setting-tabs">
<template #rightExtra>
<a-button type="primary" @click="handleAddConfig">
<template #icon>
<PlusOutlined />
</template>
新增配置
</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" :required="field.required">
<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 || '请输入'" allow-clear />
<!-- 文本域 -->
<a-textarea v-else-if="field.type === 'textarea'"
v-model:value="formData[field.name]"
:placeholder="field.placeholder || '请输入'" :rows="4"
:maxlength="field.maxLength" :show-count="field.maxLength > 0"
allow-clear />
<!-- 数字输入 -->
<a-input-number v-else-if="field.type === 'number'"
v-model:value="formData[field.name]"
:placeholder="field.placeholder || '请输入'" :min="field.min" :max="field.max"
:precision="field.precision || 0" style="width: 100%" />
<!-- 开关 -->
<a-switch v-else-if="field.type === 'switch'"
v-model:checked="formData[field.name]" :checked-children="启用"
:un-checked-children="禁用" />
<!-- 下拉选择 -->
<a-select v-else-if="field.type === 'select'"
v-model:value="formData[field.name]"
:placeholder="field.placeholder || '请选择'" 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-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 class="form-actions">
<a-tooltip title="编辑配置项">
<EditOutlined class="action-icon edit-icon" @click="handleEditField(field)" />
</a-tooltip>
<!-- 图片上传 -->
<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 v-if="field.tip" class="field-tip">
<InfoCircleOutlined class="tip-icon" />
{{ field.tip }}
<div class="form-actions">
<a-tooltip title="编辑配置项">
<EditOutlined class="action-icon edit-icon"
@click="handleEditField(field)" />
</a-tooltip>
</div>
</a-form-item>
</a-form>
</div>
<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
v-if="fields.filter(f => f.category === category.name).length === 0"
description="暂无配置项"
>
<a-button type="primary" @click="handleAddConfig">
<template #icon><PlusOutlined /></template>
添加配置
</a-button>
</a-empty>
</a-tab-pane>
</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>
<!-- 空状态 -->
<a-empty v-if="fields.filter(f => f.category === category.name).length === 0"
description="暂无配置项">
<a-button type="primary" @click="handleAddConfig">
<template #icon>
<PlusOutlined />
</template>
添加配置
</a-button>
</a-empty>
</a-tab-pane>
</a-tabs>
</div>
<!-- 配置弹窗 -->
<ConfigModal
v-model:visible="modalVisible"
:is-edit="isEditMode"
:categories="categories"
:initial-data="currentEditData"
@confirm="handleModalConfirm"
/>
<ConfigModal v-model:visible="modalVisible" :is-edit="isEditMode" :categories="categories" :initial-data="currentEditData" @confirm="handleModalConfirm" />
</div>
</template>
@@ -465,95 +400,53 @@ onMounted(() => {
overflow: hidden;
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 {
flex: 1;
overflow: hidden;
padding: 16px 24px 0;
padding: 16px;
.setting-card {
.setting-tabs {
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
background: #ffffff;
padding: 0 10px;
border-radius: 10px;
:deep(.ant-card-body) {
flex: 1;
overflow: hidden;
padding: 24px;
display: flex;
flex-direction: column;
:deep(.ant-tabs-tab-btn) {
padding: 0 15px;
}
.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%;
display: flex;
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;
}
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>