更新
This commit is contained in:
@@ -1,13 +1,341 @@
|
||||
import request from '../utils/request'
|
||||
import request from "@/utils/request";
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @returns {Promise} 菜单数据
|
||||
*/
|
||||
export function upload(params) {
|
||||
return request({
|
||||
url: '/system/file/upload',
|
||||
method: 'post',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
export default {
|
||||
version: {
|
||||
url: `system/index/version`,
|
||||
name: "获取最新版本号",
|
||||
get: async function () {
|
||||
return await request.get(this.url);
|
||||
},
|
||||
},
|
||||
clearcache: {
|
||||
url: `system/index/clearcache`,
|
||||
name: "清除缓存",
|
||||
post: async function () {
|
||||
return await request.post(this.url);
|
||||
},
|
||||
},
|
||||
info: {
|
||||
url: `system/index/info`,
|
||||
name: "系统信息",
|
||||
get: function (data) {
|
||||
return request.get(this.url, data);
|
||||
},
|
||||
},
|
||||
setting: {
|
||||
list: {
|
||||
url: `system/setting/index`,
|
||||
name: "获取配置信息",
|
||||
get: function (params) {
|
||||
return request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
url: `system/setting/fields`,
|
||||
name: "获取配置字段",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
add: {
|
||||
url: `system/setting/add`,
|
||||
name: "保存配置信息",
|
||||
post: function (data) {
|
||||
return request.post(this.url, data);
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
url: `system/setting/edit`,
|
||||
name: "编辑配置信息",
|
||||
post: function (data) {
|
||||
return request.put(this.url, data);
|
||||
},
|
||||
},
|
||||
save: {
|
||||
url: `system/setting/save`,
|
||||
name: "保存配置信息",
|
||||
post: function (data) {
|
||||
return request.put(this.url, data);
|
||||
},
|
||||
},
|
||||
},
|
||||
dictionary: {
|
||||
category: {
|
||||
url: `system/dict/category`,
|
||||
name: "获取字典树",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
editcate: {
|
||||
url: `system/dict/editcate`,
|
||||
name: "编辑字典树",
|
||||
post: async function (data = {}) {
|
||||
return await request.put(this.url, data);
|
||||
},
|
||||
},
|
||||
addcate: {
|
||||
url: `system/dict/addcate`,
|
||||
name: "添加字典树",
|
||||
post: async function (data = {}) {
|
||||
return await request.post(this.url, data);
|
||||
},
|
||||
},
|
||||
delCate: {
|
||||
url: `system/dict/deletecate`,
|
||||
name: "删除字典树",
|
||||
post: async function (data = {}) {
|
||||
return await request.delete(this.url, data);
|
||||
},
|
||||
},
|
||||
list: {
|
||||
url: `system/dict/lists`,
|
||||
name: "字典明细",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
get: {
|
||||
url: `system/dict/detail`,
|
||||
name: "获取字典数据",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
url: `system/dict/edit`,
|
||||
name: "编辑字典明细",
|
||||
post: async function (data = {}) {
|
||||
return await request.put(this.url, data);
|
||||
},
|
||||
},
|
||||
add: {
|
||||
url: `system/dict/add`,
|
||||
name: "添加字典明细",
|
||||
post: async function (data = {}) {
|
||||
return await request.post(this.url, data);
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
url: `system/dict/delete`,
|
||||
name: "删除字典明细",
|
||||
post: async function (data = {}) {
|
||||
return await request.delete(this.url, data);
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
url: `system/dict/detail`,
|
||||
name: "字典明细",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
alldic: {
|
||||
url: `system/dict/all`,
|
||||
name: "全部字典",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
},
|
||||
area: {
|
||||
list: {
|
||||
url: `system/area/index`,
|
||||
name: "地区列表",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
add: {
|
||||
url: `system/area/add`,
|
||||
name: "地区添加",
|
||||
post: async function (params) {
|
||||
return await request.post(this.url, params);
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
url: `system/area/edit`,
|
||||
name: "地区编辑",
|
||||
post: async function (params) {
|
||||
return await request.put(this.url, params);
|
||||
},
|
||||
},
|
||||
},
|
||||
app: {
|
||||
list: {
|
||||
url: `system/app/list`,
|
||||
name: "应用列表",
|
||||
get: async function () {
|
||||
return await request.get(this.url);
|
||||
},
|
||||
},
|
||||
},
|
||||
client: {
|
||||
list: {
|
||||
url: `system/client/index`,
|
||||
name: "客户端列表",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
add: {
|
||||
url: `system/client/add`,
|
||||
name: "客户端添加",
|
||||
post: async function (params) {
|
||||
return await request.post(this.url, params);
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
url: `system/client/edit`,
|
||||
name: "客户端编辑",
|
||||
post: async function (params) {
|
||||
return await request.put(this.url, params);
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
url: `system/client/delete`,
|
||||
name: "客户端删除",
|
||||
post: async function (params) {
|
||||
return await request.delete(this.url, params);
|
||||
},
|
||||
},
|
||||
menu: {
|
||||
list: {
|
||||
url: `system/menu/index`,
|
||||
name: "客户端菜单列表",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
add: {
|
||||
url: `system/menu/add`,
|
||||
name: "客户端菜单添加",
|
||||
post: async function (params) {
|
||||
return await request.post(this.url, params);
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
url: `system/menu/edit`,
|
||||
name: "客户端菜单编辑",
|
||||
post: async function (params) {
|
||||
return await request.put(this.url, params);
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
url: `system/menu/delete`,
|
||||
name: "客户端菜单删除",
|
||||
post: async function (params) {
|
||||
return await request.delete(this.url, params);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
log: {
|
||||
list: {
|
||||
url: `system/log/index`,
|
||||
name: "日志列表",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
my: {
|
||||
url: `system/log/my`,
|
||||
name: "我的日志",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
url: `system/log/delete`,
|
||||
name: "日志删除",
|
||||
post: async function (params) {
|
||||
return await request.delete(this.url, params);
|
||||
},
|
||||
},
|
||||
},
|
||||
tasks: {
|
||||
list: {
|
||||
url: `system/tasks/index`,
|
||||
name: "任务列表",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
url: `system/tasks/delete`,
|
||||
name: "任务删除",
|
||||
post: async function (params) {
|
||||
return await request.delete(this.url, params);
|
||||
},
|
||||
},
|
||||
},
|
||||
crontab: {
|
||||
list: {
|
||||
url: `system/crontab/index`,
|
||||
name: "定时任务列表",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
add: {
|
||||
url: `system/crontab/add`,
|
||||
name: "定时任务添加",
|
||||
post: async function (params) {
|
||||
return await request.post(this.url, params);
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
url: `system/crontab/edit`,
|
||||
name: "定时任务编辑",
|
||||
post: async function (params) {
|
||||
return await request.put(this.url, params);
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
url: `system/crontab/delete`,
|
||||
name: "定时任务删除",
|
||||
post: async function (params) {
|
||||
return await request.delete(this.url, params);
|
||||
},
|
||||
},
|
||||
log: {
|
||||
url: `system/crontab/log`,
|
||||
name: "定时任务日志",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
reload: {
|
||||
url: `system/crontab/reload`,
|
||||
name: "定时任务重载",
|
||||
post: async function (params) {
|
||||
return await request.put(this.url, params);
|
||||
},
|
||||
},
|
||||
},
|
||||
modules: {
|
||||
list: {
|
||||
url: `system/modules/index`,
|
||||
name: "模块列表",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
update: {
|
||||
url: `system/modules/update`,
|
||||
name: "更新模块",
|
||||
post: async function (params) {
|
||||
return await request.post(this.url, params);
|
||||
},
|
||||
},
|
||||
},
|
||||
sms: {
|
||||
count: {
|
||||
url: `system/sms/count`,
|
||||
name: "短信发送统计",
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,6 +5,37 @@ export default {
|
||||
logout: 'Logout',
|
||||
register: 'Register',
|
||||
searchMenu: 'Search Menu',
|
||||
searchPlaceholder: 'Please enter menu name to search',
|
||||
noResults: 'No matching menus found',
|
||||
searchTips: 'Keyboard Shortcuts Tips',
|
||||
navigateResults: 'Use up/down arrows to navigate',
|
||||
selectResult: 'Press Enter to select',
|
||||
closeSearch: 'Press ESC to close',
|
||||
taskCenter: 'Task Center',
|
||||
totalTasks: 'Total Tasks',
|
||||
pendingTasks: 'Pending',
|
||||
completedTasks: 'Completed',
|
||||
searchTasks: 'Search tasks...',
|
||||
all: 'All',
|
||||
pending: 'Pending',
|
||||
completed: 'Completed',
|
||||
taskTitle: 'Task Title',
|
||||
enterTaskTitle: 'Please enter task title',
|
||||
taskPriority: 'Task Priority',
|
||||
priorityHigh: 'High',
|
||||
priorityMedium: 'Medium',
|
||||
priorityLow: 'Low',
|
||||
confirmDelete: 'Confirm Delete',
|
||||
addTask: 'Add Task',
|
||||
pleaseEnterTaskTitle: 'Please enter task title',
|
||||
added: 'Added',
|
||||
deleted: 'Deleted',
|
||||
justNow: 'Just now',
|
||||
clearCache: 'Clear Cache',
|
||||
confirmClearCache: 'Confirm Clear Cache',
|
||||
clearCacheConfirm: 'Are you sure you want to clear all cache? This will clear local storage, session storage and cached data.',
|
||||
cacheCleared: 'Cache cleared',
|
||||
clearCacheFailed: 'Failed to clear cache',
|
||||
messages: 'Messages',
|
||||
tasks: 'Tasks',
|
||||
clearAll: 'Clear All',
|
||||
@@ -62,6 +93,33 @@ export default {
|
||||
info: 'Info',
|
||||
confirmDelete: 'Are you sure you want to delete?',
|
||||
confirmLogout: 'Are you sure you want to logout?',
|
||||
addConfig: 'Add Config',
|
||||
editConfig: 'Edit Config',
|
||||
configCategory: 'Config Category',
|
||||
configName: 'Config Name',
|
||||
configTitle: 'Config Title',
|
||||
configType: 'Config Type',
|
||||
configValue: 'Config Value',
|
||||
configTip: 'Config Tip',
|
||||
typeText: 'Text',
|
||||
typeTextarea: 'Textarea',
|
||||
typeNumber: 'Number',
|
||||
typeSwitch: 'Switch',
|
||||
typeSelect: 'Select',
|
||||
typeMultiselect: 'Multiselect',
|
||||
typeDatetime: 'Datetime',
|
||||
typeColor: 'Color',
|
||||
pleaseSelect: 'Please Select',
|
||||
pleaseEnter: 'Please Enter',
|
||||
noConfig: 'No Config',
|
||||
fetchConfigFailed: 'Failed to fetch config',
|
||||
addSuccess: 'Added Successfully',
|
||||
addFailed: 'Failed to Add',
|
||||
editSuccess: 'Edited Successfully',
|
||||
editFailed: 'Failed to Edit',
|
||||
saveSuccess: 'Saved Successfully',
|
||||
saveFailed: 'Failed to Save',
|
||||
resetSuccess: 'Reset Successfully',
|
||||
required: 'This field is required',
|
||||
operation: 'Operation',
|
||||
time: 'Time',
|
||||
|
||||
@@ -5,6 +5,37 @@ export default {
|
||||
logout: '退出登录',
|
||||
register: '注册',
|
||||
searchMenu: '搜索菜单',
|
||||
searchPlaceholder: '请输入菜单名称进行搜索',
|
||||
noResults: '未找到匹配的菜单',
|
||||
searchTips: '快捷键操作提示',
|
||||
navigateResults: '使用上下键导航',
|
||||
selectResult: '按回车键选择',
|
||||
closeSearch: '按 ESC 关闭',
|
||||
taskCenter: '任务中心',
|
||||
totalTasks: '总任务',
|
||||
pendingTasks: '待完成',
|
||||
completedTasks: '已完成',
|
||||
searchTasks: '搜索任务...',
|
||||
all: '全部',
|
||||
pending: '待完成',
|
||||
completed: '已完成',
|
||||
taskTitle: '任务标题',
|
||||
enterTaskTitle: '请输入任务标题',
|
||||
taskPriority: '任务优先级',
|
||||
priorityHigh: '高',
|
||||
priorityMedium: '中',
|
||||
priorityLow: '低',
|
||||
confirmDelete: '确认删除',
|
||||
addTask: '添加任务',
|
||||
pleaseEnterTaskTitle: '请输入任务标题',
|
||||
added: '已添加',
|
||||
deleted: '已删除',
|
||||
justNow: '刚刚',
|
||||
clearCache: '清除缓存',
|
||||
confirmClearCache: '确认清除缓存',
|
||||
clearCacheConfirm: '确定要清除所有缓存吗?这将清除本地存储、会话存储和缓存数据。',
|
||||
cacheCleared: '缓存已清除',
|
||||
clearCacheFailed: '清除缓存失败',
|
||||
messages: '消息',
|
||||
tasks: '任务',
|
||||
clearAll: '清空全部',
|
||||
@@ -62,6 +93,33 @@ export default {
|
||||
info: '提示',
|
||||
confirmDelete: '确定要删除吗?',
|
||||
confirmLogout: '确定要退出登录吗?',
|
||||
addConfig: '添加配置',
|
||||
editConfig: '编辑配置',
|
||||
configCategory: '配置分类',
|
||||
configName: '配置名称',
|
||||
configTitle: '配置标题',
|
||||
configType: '配置类型',
|
||||
configValue: '配置值',
|
||||
configTip: '配置提示',
|
||||
typeText: '文本',
|
||||
typeTextarea: '文本域',
|
||||
typeNumber: '数字',
|
||||
typeSwitch: '开关',
|
||||
typeSelect: '下拉选择',
|
||||
typeMultiselect: '多选',
|
||||
typeDatetime: '日期时间',
|
||||
typeColor: '颜色',
|
||||
pleaseSelect: '请选择',
|
||||
pleaseEnter: '请输入',
|
||||
noConfig: '暂无配置',
|
||||
fetchConfigFailed: '获取配置失败',
|
||||
addSuccess: '添加成功',
|
||||
addFailed: '添加失败',
|
||||
editSuccess: '编辑成功',
|
||||
editFailed: '编辑失败',
|
||||
saveSuccess: '保存成功',
|
||||
saveFailed: '保存失败',
|
||||
resetSuccess: '重置成功',
|
||||
required: '此项为必填项',
|
||||
operation: '操作',
|
||||
time: '时间',
|
||||
|
||||
302
src/layouts/components/search.vue
Normal file
302
src/layouts/components/search.vue
Normal file
@@ -0,0 +1,302 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:open="visible"
|
||||
:title="$t('common.searchMenu')"
|
||||
:footer="null"
|
||||
:width="600"
|
||||
:destroyOnClose="true"
|
||||
@cancel="handleClose"
|
||||
>
|
||||
<div class="menu-search">
|
||||
<a-input
|
||||
v-model:value="searchKeyword"
|
||||
:placeholder="$t('common.searchPlaceholder')"
|
||||
size="large"
|
||||
allow-clear
|
||||
@input="handleSearch"
|
||||
@keydown="handleKeydown"
|
||||
ref="searchInputRef"
|
||||
>
|
||||
<template #prefix>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
|
||||
<div v-if="searchResults.length > 0" class="search-results">
|
||||
<div
|
||||
v-for="(item, index) in searchResults"
|
||||
:key="item.path"
|
||||
class="result-item"
|
||||
:class="{ active: selectedIndex === index }"
|
||||
@click="handleSelect(item)"
|
||||
@mouseenter="selectedIndex = index"
|
||||
>
|
||||
<div class="result-icon">
|
||||
<component :is="item.icon || 'MenuOutlined'" />
|
||||
</div>
|
||||
<div class="result-content">
|
||||
<div class="result-title">{{ item.title }}</div>
|
||||
<div v-if="item.breadcrumbs" class="result-path">{{ item.breadcrumbs }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="searchKeyword" class="no-results">
|
||||
<a-empty :description="$t('common.noResults')" />
|
||||
</div>
|
||||
|
||||
<div v-else class="search-tips">
|
||||
<div class="tip-title">{{ $t('common.searchTips') }}</div>
|
||||
<div class="tip-list">
|
||||
<div class="tip-item">
|
||||
<kbd>↑</kbd>
|
||||
<kbd>↓</kbd>
|
||||
<span>{{ $t('common.navigateResults') }}</span>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<kbd>Enter</kbd>
|
||||
<span>{{ $t('common.selectResult') }}</span>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<kbd>Esc</kbd>
|
||||
<span>{{ $t('common.closeSearch') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { SearchOutlined, MenuOutlined } from '@ant-design/icons-vue'
|
||||
import { useUserStore } from '@/stores/modules/user'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 定义组件名称
|
||||
defineOptions({
|
||||
name: 'MenuSearch',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const visible = defineModel('visible', { type: Boolean, default: false })
|
||||
const searchKeyword = ref('')
|
||||
const searchResults = ref([])
|
||||
const selectedIndex = ref(0)
|
||||
const searchInputRef = ref(null)
|
||||
|
||||
// 将扁平化的菜单数据转换为可搜索格式
|
||||
function flattenMenus(menus, breadcrumbs = []) {
|
||||
const result = []
|
||||
|
||||
menus.forEach((menu) => {
|
||||
if (menu.hidden) return
|
||||
|
||||
const currentBreadcrumbs = [...breadcrumbs, menu.title]
|
||||
|
||||
// 如果有路径且不是外部链接,添加到搜索结果
|
||||
if (menu.path && !menu.path.startsWith('http')) {
|
||||
result.push({
|
||||
title: menu.title,
|
||||
path: menu.path,
|
||||
icon: menu.icon,
|
||||
breadcrumbs: currentBreadcrumbs.join(' / '),
|
||||
})
|
||||
}
|
||||
|
||||
// 递归处理子菜单
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
const children = flattenMenus(menu.children, currentBreadcrumbs)
|
||||
result.push(...children)
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 获取所有菜单项
|
||||
const allMenus = computed(() => {
|
||||
const menus = userStore.menu || []
|
||||
return flattenMenus(menus)
|
||||
})
|
||||
|
||||
// 执行搜索
|
||||
function handleSearch() {
|
||||
if (!searchKeyword.value.trim()) {
|
||||
searchResults.value = []
|
||||
selectedIndex.value = 0
|
||||
return
|
||||
}
|
||||
|
||||
const keyword = searchKeyword.value.toLowerCase().trim()
|
||||
searchResults.value = allMenus.value.filter((menu) => {
|
||||
return menu.title.toLowerCase().includes(keyword) ||
|
||||
menu.breadcrumbs.toLowerCase().includes(keyword)
|
||||
})
|
||||
|
||||
selectedIndex.value = 0
|
||||
}
|
||||
|
||||
// 键盘导航
|
||||
function handleKeydown(e) {
|
||||
if (!searchResults.value.length) return
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowUp':
|
||||
e.preventDefault()
|
||||
selectedIndex.value = selectedIndex.value > 0
|
||||
? selectedIndex.value - 1
|
||||
: searchResults.value.length - 1
|
||||
break
|
||||
case 'ArrowDown':
|
||||
e.preventDefault()
|
||||
selectedIndex.value = selectedIndex.value < searchResults.value.length - 1
|
||||
? selectedIndex.value + 1
|
||||
: 0
|
||||
break
|
||||
case 'Enter':
|
||||
e.preventDefault()
|
||||
if (searchResults.value[selectedIndex.value]) {
|
||||
handleSelect(searchResults.value[selectedIndex.value])
|
||||
}
|
||||
break
|
||||
case 'Escape':
|
||||
e.preventDefault()
|
||||
handleClose()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 选择菜单项
|
||||
function handleSelect(item) {
|
||||
visible.value = false
|
||||
router.push(item.path)
|
||||
}
|
||||
|
||||
// 关闭搜索弹窗
|
||||
function handleClose() {
|
||||
visible.value = false
|
||||
searchKeyword.value = ''
|
||||
searchResults.value = []
|
||||
selectedIndex.value = 0
|
||||
}
|
||||
|
||||
// 监听弹窗显示,自动聚焦输入框
|
||||
watch(visible, (newVal) => {
|
||||
if (newVal) {
|
||||
nextTick(() => {
|
||||
searchInputRef.value?.focus()
|
||||
})
|
||||
} else {
|
||||
handleClose()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.menu-search {
|
||||
.search-results {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
margin-top: 16px;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 4px;
|
||||
|
||||
.result-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: #e6f7ff;
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
margin-right: 12px;
|
||||
font-size: 16px;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.result-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.result-title {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.result-path {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-results {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.search-tips {
|
||||
margin-top: 20px;
|
||||
|
||||
.tip-title {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tip-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
|
||||
.tip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 16px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
|
||||
kbd {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
line-height: 1;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
448
src/layouts/components/task.vue
Normal file
448
src/layouts/components/task.vue
Normal file
@@ -0,0 +1,448 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
v-model:open="visible"
|
||||
:title="$t('common.taskCenter')"
|
||||
placement="right"
|
||||
:width="400"
|
||||
:destroyOnClose="true"
|
||||
>
|
||||
<div class="task-drawer">
|
||||
<!-- 任务统计 -->
|
||||
<div class="task-stats">
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">{{ totalTasks }}</div>
|
||||
<div class="stat-label">{{ $t('common.totalTasks') }}</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number pending">{{ pendingTasks }}</div>
|
||||
<div class="stat-label">{{ $t('common.pendingTasks') }}</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number completed">{{ completedTasks }}</div>
|
||||
<div class="stat-label">{{ $t('common.completedTasks') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<div class="task-actions">
|
||||
<a-input
|
||||
v-model:value="searchKeyword"
|
||||
:placeholder="$t('common.searchTasks')"
|
||||
allow-clear
|
||||
@input="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
<div class="filter-buttons">
|
||||
<a-button
|
||||
:type="filterType === 'all' ? 'primary' : 'default'"
|
||||
size="small"
|
||||
@click="setFilter('all')"
|
||||
>
|
||||
{{ $t('common.all') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
:type="filterType === 'pending' ? 'primary' : 'default'"
|
||||
size="small"
|
||||
@click="setFilter('pending')"
|
||||
>
|
||||
{{ $t('common.pending') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
:type="filterType === 'completed' ? 'primary' : 'default'"
|
||||
size="small"
|
||||
@click="setFilter('completed')"
|
||||
>
|
||||
{{ $t('common.completed') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务列表 -->
|
||||
<div class="task-list">
|
||||
<div v-if="filteredTasks.length > 0">
|
||||
<div
|
||||
v-for="task in filteredTasks"
|
||||
:key="task.id"
|
||||
class="task-item"
|
||||
:class="{ completed: task.completed }"
|
||||
>
|
||||
<div class="task-checkbox">
|
||||
<a-checkbox
|
||||
:checked="task.completed"
|
||||
@change="toggleTask(task)"
|
||||
/>
|
||||
</div>
|
||||
<div class="task-content">
|
||||
<div class="task-title">{{ task.title }}</div>
|
||||
<div class="task-meta">
|
||||
<span class="task-priority" :class="task.priority">
|
||||
{{ $t(`common.priority${task.priority.charAt(0).toUpperCase() + task.priority.slice(1)}`) }}
|
||||
</span>
|
||||
<span class="task-time">{{ task.time }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-actions">
|
||||
<a-popconfirm
|
||||
:title="$t('common.confirmDelete')"
|
||||
:ok-text="$t('common.confirm')"
|
||||
:cancel-text="$t('common.cancel')"
|
||||
@confirm="deleteTask(task.id)"
|
||||
>
|
||||
<DeleteOutlined class="action-icon" />
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-empty v-else :description="$t('common.noTasks')" />
|
||||
</div>
|
||||
|
||||
<!-- 底部操作 -->
|
||||
<div class="drawer-footer">
|
||||
<a-button @click="showAddTask">
|
||||
<PlusOutlined />
|
||||
{{ $t('common.addTask') }}
|
||||
</a-button>
|
||||
<a-button danger @click="clearAllTasks">
|
||||
{{ $t('common.clearAll') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加任务弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="addTaskVisible"
|
||||
:title="$t('common.addTask')"
|
||||
:ok-text="$t('common.confirm')"
|
||||
:cancel-text="$t('common.cancel')"
|
||||
@ok="confirmAddTask"
|
||||
>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item :label="$t('common.taskTitle')">
|
||||
<a-input v-model:value="newTask.title" :placeholder="$t('common.enterTaskTitle')" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('common.taskPriority')">
|
||||
<a-select v-model:value="newTask.priority">
|
||||
<a-select-option value="low">{{ $t('common.priorityLow') }}</a-select-option>
|
||||
<a-select-option value="medium">{{ $t('common.priorityMedium') }}</a-select-option>
|
||||
<a-select-option value="high">{{ $t('common.priorityHigh') }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { SearchOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 定义组件名称
|
||||
defineOptions({
|
||||
name: 'TaskDrawer',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const visible = defineModel('visible', { type: Boolean, default: false })
|
||||
|
||||
const tasks = defineModel('tasks', { type: Array, default: () => [] })
|
||||
|
||||
// 搜索关键词
|
||||
const searchKeyword = ref('')
|
||||
// 筛选类型:all, pending, completed
|
||||
const filterType = ref('all')
|
||||
|
||||
// 添加任务弹窗
|
||||
const addTaskVisible = ref(false)
|
||||
const newTask = ref({
|
||||
title: '',
|
||||
priority: 'medium',
|
||||
})
|
||||
|
||||
// 统计数据
|
||||
const totalTasks = computed(() => tasks.value.length)
|
||||
const pendingTasks = computed(() => tasks.value.filter(t => !t.completed).length)
|
||||
const completedTasks = computed(() => tasks.value.filter(t => t.completed).length)
|
||||
|
||||
// 筛选后的任务列表
|
||||
const filteredTasks = computed(() => {
|
||||
let result = [...tasks.value]
|
||||
|
||||
// 按状态筛选
|
||||
if (filterType.value === 'pending') {
|
||||
result = result.filter(t => !t.completed)
|
||||
} else if (filterType.value === 'completed') {
|
||||
result = result.filter(t => t.completed)
|
||||
}
|
||||
|
||||
// 按关键词搜索
|
||||
if (searchKeyword.value.trim()) {
|
||||
const keyword = searchKeyword.value.toLowerCase()
|
||||
result = result.filter(t =>
|
||||
t.title.toLowerCase().includes(keyword)
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
// 切换任务状态
|
||||
const toggleTask = (task) => {
|
||||
task.completed = !task.completed
|
||||
}
|
||||
|
||||
// 删除任务
|
||||
const deleteTask = (id) => {
|
||||
const index = tasks.value.findIndex(t => t.id === id)
|
||||
if (index > -1) {
|
||||
tasks.value.splice(index, 1)
|
||||
message.success(t('common.deleted'))
|
||||
}
|
||||
}
|
||||
|
||||
// 清空所有任务
|
||||
const clearAllTasks = () => {
|
||||
tasks.value = []
|
||||
message.success(t('common.cleared'))
|
||||
}
|
||||
|
||||
// 显示添加任务弹窗
|
||||
const showAddTask = () => {
|
||||
newTask.value = {
|
||||
title: '',
|
||||
priority: 'medium',
|
||||
}
|
||||
addTaskVisible.value = true
|
||||
}
|
||||
|
||||
// 确认添加任务
|
||||
const confirmAddTask = () => {
|
||||
if (!newTask.value.title.trim()) {
|
||||
message.warning(t('common.pleaseEnterTaskTitle'))
|
||||
return
|
||||
}
|
||||
|
||||
tasks.value.unshift({
|
||||
id: Date.now(),
|
||||
title: newTask.value.title,
|
||||
priority: newTask.value.priority,
|
||||
completed: false,
|
||||
time: t('common.justNow'),
|
||||
})
|
||||
|
||||
addTaskVisible.value = false
|
||||
message.success(t('common.added'))
|
||||
}
|
||||
|
||||
// 设置筛选类型
|
||||
const setFilter = (type) => {
|
||||
filterType.value = type
|
||||
}
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
// 搜索逻辑在 computed 中自动处理
|
||||
}
|
||||
|
||||
// 监听抽窗关闭,重置搜索和筛选
|
||||
watch(visible, (newVal) => {
|
||||
if (!newVal) {
|
||||
searchKeyword.value = ''
|
||||
filterType.value = 'all'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.task-drawer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.task-stats {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
padding: 16px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 8px;
|
||||
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
|
||||
.stat-number {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&.pending {
|
||||
color: #ffd666;
|
||||
}
|
||||
|
||||
&.completed {
|
||||
color: #95de64;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.task-actions {
|
||||
margin-bottom: 16px;
|
||||
|
||||
:deep(.ant-input-affix-wrapper) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.filter-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
.ant-btn {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.task-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 16px;
|
||||
padding-right: 8px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
}
|
||||
|
||||
.task-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #f0f0f0;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
&.completed {
|
||||
.task-title {
|
||||
text-decoration: line-through;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.task-content {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.task-checkbox {
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.task-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.task-title {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
|
||||
.task-priority {
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-weight: 500;
|
||||
|
||||
&.high {
|
||||
background-color: #fff1f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
&.medium {
|
||||
background-color: #fff7e6;
|
||||
color: #fa8c16;
|
||||
}
|
||||
|
||||
&.low {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
}
|
||||
|
||||
.task-time {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.task-actions {
|
||||
margin-left: 8px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.action-icon {
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-footer {
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
.ant-btn {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -34,28 +34,13 @@
|
||||
</a-dropdown>
|
||||
|
||||
<!-- 任务列表 -->
|
||||
<a-dropdown :trigger="['click']" placement="bottomRight">
|
||||
<a-tooltip :title="$t('common.taskCenter')">
|
||||
<a-badge :count="taskCount" :offset="[-5, 5]">
|
||||
<a-button type="text" class="action-btn">
|
||||
<a-button type="text" @click="taskVisible = true" class="action-btn">
|
||||
<CheckSquareOutlined />
|
||||
</a-button>
|
||||
</a-badge>
|
||||
<template #overlay>
|
||||
<a-card class="dropdown-card" :title="$t('common.tasks')" :bordered="false">
|
||||
<template #extra>
|
||||
<a @click="clearTasks">{{ $t('common.clearAll') }}</a>
|
||||
</template>
|
||||
<div class="task-list">
|
||||
<div v-for="task in tasks" :key="task.id" class="task-item">
|
||||
<a-checkbox :checked="task.completed" @change="toggleTask(task)">
|
||||
<span :class="{ completed: task.completed }">{{ task.title }}</span>
|
||||
</a-checkbox>
|
||||
</div>
|
||||
<a-empty v-if="tasks.length === 0" :description="$t('common.noTasks')" />
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
|
||||
<!-- 语言切换 -->
|
||||
<a-dropdown :trigger="['click']" placement="bottomRight">
|
||||
@@ -99,6 +84,10 @@
|
||||
<SettingOutlined />
|
||||
<span>{{ $t('common.systemSettings') }}</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="clearCache">
|
||||
<DeleteOutlined />
|
||||
<span>{{ $t('common.clearCache') }}</span>
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="logout">
|
||||
<LogoutOutlined />
|
||||
@@ -108,6 +97,12 @@
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
|
||||
<!-- 菜单搜索弹窗 -->
|
||||
<search v-model:visible="searchVisible" />
|
||||
|
||||
<!-- 任务抽屉 -->
|
||||
<task v-model:visible="taskVisible" v-model:tasks="tasks" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -116,8 +111,10 @@ import { useRouter } from 'vue-router'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { useUserStore } from '@/stores/modules/user'
|
||||
import { useI18nStore } from '@/stores/modules/i18n'
|
||||
import { DownOutlined, UserOutlined, LogoutOutlined, FullscreenOutlined, FullscreenExitOutlined, BellOutlined, CheckSquareOutlined, GlobalOutlined, SearchOutlined, SettingOutlined } from '@ant-design/icons-vue'
|
||||
import { DownOutlined, UserOutlined, LogoutOutlined, FullscreenOutlined, FullscreenExitOutlined, BellOutlined, CheckSquareOutlined, GlobalOutlined, SearchOutlined, SettingOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import search from './search.vue'
|
||||
import task from './task.vue'
|
||||
|
||||
// 定义组件名称(多词命名)
|
||||
defineOptions({
|
||||
@@ -130,6 +127,8 @@ const userStore = useUserStore()
|
||||
const i18nStore = useI18nStore()
|
||||
|
||||
const isFullscreen = ref(false)
|
||||
const searchVisible = ref(false)
|
||||
const taskVisible = ref(false)
|
||||
|
||||
// 消息数据
|
||||
const messages = ref([
|
||||
@@ -143,9 +142,9 @@ const messageCount = computed(() => messages.value.filter((m) => !m.read).length
|
||||
|
||||
// 任务数据
|
||||
const tasks = ref([
|
||||
{ id: 1, title: '完成用户审核', completed: false },
|
||||
{ id: 2, title: '更新系统文档', completed: false },
|
||||
{ id: 3, title: '优化数据库查询', completed: true },
|
||||
{ id: 1, title: '完成用户审核', priority: 'high', completed: false, time: '今天' },
|
||||
{ id: 2, title: '更新系统文档', priority: 'medium', completed: false, time: '明天' },
|
||||
{ id: 3, title: '优化数据库查询', priority: 'low', completed: true, time: '昨天' },
|
||||
])
|
||||
|
||||
const taskCount = computed(() => tasks.value.filter((t) => !t.completed).length)
|
||||
@@ -176,7 +175,7 @@ onUnmounted(() => {
|
||||
|
||||
// 显示搜索功能
|
||||
const showSearch = () => {
|
||||
message.info('搜索功能开发中')
|
||||
searchVisible.value = true
|
||||
}
|
||||
|
||||
// 清除消息
|
||||
@@ -185,15 +184,9 @@ const clearMessages = () => {
|
||||
message.success(t('common.cleared'))
|
||||
}
|
||||
|
||||
// 清除任务
|
||||
const clearTasks = () => {
|
||||
tasks.value = []
|
||||
message.success(t('common.cleared'))
|
||||
}
|
||||
|
||||
// 切换任务状态
|
||||
const toggleTask = (task) => {
|
||||
task.completed = !task.completed
|
||||
// 显示任务抽屉
|
||||
const showTasks = () => {
|
||||
taskVisible.value = true
|
||||
}
|
||||
|
||||
// 切换语言
|
||||
@@ -209,8 +202,10 @@ const handleMenuClick = ({ key }) => {
|
||||
router.push('/ucenter')
|
||||
break
|
||||
case 'settings':
|
||||
// 系统设置功能暂未实现
|
||||
message.info(t('common.settingsDeveloping'))
|
||||
router.push('/system/setting')
|
||||
break
|
||||
case 'clearCache':
|
||||
handleClearCache()
|
||||
break
|
||||
case 'logout':
|
||||
handleLogout()
|
||||
@@ -218,6 +213,40 @@ const handleMenuClick = ({ key }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
const handleClearCache = () => {
|
||||
Modal.confirm({
|
||||
title: t('common.confirmClearCache'),
|
||||
content: t('common.clearCacheConfirm'),
|
||||
okText: t('common.confirm'),
|
||||
cancelText: t('common.cancel'),
|
||||
onOk: () => {
|
||||
try {
|
||||
// 清除 localStorage
|
||||
localStorage.clear()
|
||||
// 清除 sessionStorage
|
||||
sessionStorage.clear()
|
||||
// 清除所有缓存
|
||||
if ('caches' in window) {
|
||||
caches.keys().then(names => {
|
||||
names.forEach(name => {
|
||||
caches.delete(name)
|
||||
})
|
||||
})
|
||||
}
|
||||
message.success(t('common.cacheCleared'))
|
||||
// 延迟刷新页面以应用缓存清除
|
||||
setTimeout(() => {
|
||||
window.location.reload()
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
message.error(t('common.clearCacheFailed'))
|
||||
console.error('清除缓存失败:', error)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
const handleLogout = () => {
|
||||
Modal.confirm({
|
||||
|
||||
137
src/pages/system/setting/components/ConfigModal.vue
Normal file
137
src/pages/system/setting/components/ConfigModal.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<a-modal :open="visible" :title="isEdit ? $t('common.editConfig') : $t('common.addConfig')" :width="600"
|
||||
:ok-text="$t('common.confirm')" :cancel-text="$t('common.cancel')" @ok="handleConfirm" @cancel="handleClose"
|
||||
:after-close="handleClose">
|
||||
<a-form ref="formRef" :model="formData" :label-col="{ span: 5 }">
|
||||
<a-form-item v-if="!isEdit" :name="['category']" :label="$t('common.configCategory')"
|
||||
:rules="[{ required: true, message: $t('common.pleaseSelect') + $t('common.configCategory') }]">
|
||||
<a-select v-model:value="formData.category" :placeholder="$t('common.pleaseSelect')">
|
||||
<a-select-option v-for="category in categories" :key="category.name" :value="category.name">
|
||||
{{ category.title }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-if="!isEdit" :name="['name']" :label="$t('common.configName')"
|
||||
:rules="[{ required: true, message: $t('common.pleaseEnter') + $t('common.configName') }]">
|
||||
<a-input v-model:value="formData.name" :placeholder="$t('common.pleaseEnter')" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :name="['title']" :label="$t('common.configTitle')"
|
||||
:rules="[{ required: true, message: $t('common.pleaseEnter') + $t('common.configTitle') }]">
|
||||
<a-input v-model:value="formData.title" :placeholder="$t('common.pleaseEnter')" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-if="!isEdit" :name="['type']" :label="$t('common.configType')"
|
||||
:rules="[{ required: true, message: $t('common.pleaseSelect') + $t('common.configType') }]">
|
||||
<a-select v-model:value="formData.type" :placeholder="$t('common.pleaseSelect')">
|
||||
<a-select-option value="text">{{ $t('common.typeText') }}</a-select-option>
|
||||
<a-select-option value="textarea">{{ $t('common.typeTextarea') }}</a-select-option>
|
||||
<a-select-option value="number">{{ $t('common.typeNumber') }}</a-select-option>
|
||||
<a-select-option value="switch">{{ $t('common.typeSwitch') }}</a-select-option>
|
||||
<a-select-option value="select">{{ $t('common.typeSelect') }}</a-select-option>
|
||||
<a-select-option value="multiselect">{{ $t('common.typeMultiselect') }}</a-select-option>
|
||||
<a-select-option value="datetime">{{ $t('common.typeDatetime') }}</a-select-option>
|
||||
<a-select-option value="color">{{ $t('common.typeColor') }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :name="['value']" :label="$t('common.configValue')">
|
||||
<a-input v-model:value="formData.value" :placeholder="$t('common.pleaseEnter')" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :name="['tip']" :label="$t('common.configTip')">
|
||||
<a-textarea v-model:value="formData.tip" :placeholder="$t('common.pleaseEnter')" :rows="3" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<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,
|
||||
default: false,
|
||||
},
|
||||
// 是否为编辑模式
|
||||
isEdit: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 配置分类列表
|
||||
categories: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
// 编辑时的初始数据
|
||||
initialData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:visible', 'confirm'])
|
||||
|
||||
const formRef = ref(null)
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
category: '',
|
||||
name: '',
|
||||
title: '',
|
||||
type: 'text',
|
||||
value: '',
|
||||
tip: '',
|
||||
})
|
||||
|
||||
// 监听弹窗显示和初始数据变化
|
||||
watch(() => props.initialData, (newVal) => {
|
||||
if (props.isEdit && Object.keys(newVal).length > 0) {
|
||||
formData.category = newVal.category || ''
|
||||
formData.name = newVal.name || ''
|
||||
formData.title = newVal.title || ''
|
||||
formData.type = newVal.type || 'text'
|
||||
formData.value = newVal.value || ''
|
||||
formData.tip = newVal.tip || ''
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 监听弹窗关闭,重置表单
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (!newVal) {
|
||||
handleClose()
|
||||
}
|
||||
})
|
||||
|
||||
// 确认提交
|
||||
const handleConfirm = async () => {
|
||||
try {
|
||||
const values = await formRef.value.validate()
|
||||
emit('confirm', values)
|
||||
} catch (error) {
|
||||
// 表单验证错误,不处理
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭弹窗并重置表单
|
||||
const handleClose = () => {
|
||||
formRef.value?.resetFields()
|
||||
formData.category = ''
|
||||
formData.name = ''
|
||||
formData.title = ''
|
||||
formData.type = 'text'
|
||||
formData.value = ''
|
||||
formData.tip = ''
|
||||
emit('update:visible', false)
|
||||
}
|
||||
</script>
|
||||
@@ -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