refactor(backend): 重构任务模块并优化菜单权限

- 移除系统模块中的任务相关代码
- 新增批量设置字典类型和数据状态的功能
- 更新菜单权限获取逻辑,增加对链接类型菜单的支持
- 调整任务模块到 monitor 监控模块下
This commit is contained in:
zhangtao
2025-07-17 00:50:07 +08:00
parent 90832d0fa2
commit d8bac53c72
58 changed files with 2101 additions and 2551 deletions
+36 -44
View File
@@ -120,15 +120,23 @@
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column prop="cache_name" label="缓存名称" align="center" :ellipsis="true">
<el-table-column prop="cache_name" label="缓存名称" show-overflow-tooltip>
<template #default="{ row }">
<el-button type="primary" link @click="getCacheKeyList(row)">{{ row.cache_name }}</el-button>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" align="center" :ellipsis="true" />
<el-table-column prop="remark" label="备注" show-overflow-tooltip />
<el-table-column label="操作" width="60" align="center">
<template #default="{ row }">
<el-button type="danger" size="small" link icon="delete" @click="handleClearCacheName(row)" />
<el-popconfirm
class="box-item"
:title="`确认删除缓存 ${row.cache_name} 吗?`"
placement="top"
>
<template #reference>
<el-button type="danger" size="small" link icon="delete" @click="handleClearCacheName(row)" />
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
@@ -162,14 +170,22 @@
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column prop="cacheKey" label="缓存键名" align="center" :ellipsis="true">
<el-table-column prop="cacheKey" label="缓存键名" show-overflow-tooltip>
<template #default="{ row }">
<el-button type="primary" link @click="handleCacheValue(row.cacheKey)">{{ row.cacheKey }}</el-button>
</template>
</el-table-column>
<el-table-column label="操作" width="60" align="center">
<template #default="{ row }">
<el-button type="danger" size="small" link icon="delete" @click="handleClearCacheKey(row.cacheKey)" />
<el-popconfirm
class="box-item"
:title="`确认删除键 ${row.cacheKey} 吗?`"
placement="top"
>
<template #reference>
<el-button type="danger" size="small" link icon="delete" @click="handleClearCacheKey(row.cacheKey)" />
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
@@ -272,25 +288,13 @@ const refreshCacheNames = () => {
};
// 清理缓存名称
const handleClearCacheName = (row: CacheInfo) => {
ElMessageBox.confirm(
`确定要清理缓存名称[${row.cache_name}]吗?`,
'确认清理',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(async () => {
const handleClearCacheName = async (row: CacheInfo) => {
try {
await CacheAPI.deleteCacheName(row.cache_name);
getCacheNameList();
})
.catch((error) => {
if (error !== 'cancel') {
console.error('清理缓存失败:', error);
}
});
refreshCacheNames();
} catch (error) {
console.error('清理缓存名称出错:', error);
}
};
// 缓存键名相关方法
@@ -321,25 +325,13 @@ const refreshCacheKeys = () => {
};
// 清理缓存键名
const handleClearCacheKey = (cacheKey: string) => {
ElMessageBox.confirm(
`确定要清理缓存键名[${cacheKey}]吗?`,
'确认清理',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
CacheAPI.deleteCacheKey(cacheKey);
const handleClearCacheKey = async (cacheKey: string) => {
try {
await CacheAPI.deleteCacheKey(cacheKey);
getCacheKeyList();
})
.catch((error) => {
if (error !== 'cancel') {
console.error('清理缓存键名失败:', error);
}
});
} catch (error) {
console.error('清理缓存键名出错:', error);
}
};
// 缓存内容相关方法
@@ -359,15 +351,15 @@ const handleCacheValue = async (cacheKey: string) => {
const handleClearCacheAll = async () => {
ElMessageBox.confirm(
'确定要清理全部缓存吗?',
'确认清理',
'危险!',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
return CacheAPI.deleteCacheAll();
.then(async () => {
return await CacheAPI.deleteCacheAll();
})
.then(() => {
getCacheNameList();
@@ -8,7 +8,7 @@
<el-input v-model="queryFormData.name" placeholder="请输入任务名称" clearable />
</el-form-item>
<el-form-item prop="status" label="状态">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable style="width: 160px">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable>
<el-option value="true" label="启用" />
<el-option value="false" label="暂停" />
</el-select>
@@ -59,30 +59,6 @@
<el-button type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0"
@click="handleOperation('delete')">批量删除</el-button>
<el-dropdown>
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多
</el-button>
<template #dropdown>
<el-menu @click="handleMoreClick">
<el-menu-item key="1">
<span>
<el-icon>
<Check />
</el-icon>
<span>批量启用</span>
</span>
</el-menu-item>
<el-menu-item key="2">
<span>
<el-icon>
<CircleClose />
</el-icon>
<span>批量停用</span>
</span>
</el-menu-item>
</el-menu>
</template>
</el-dropdown>
</div>
<div class="data-table__toolbar--tools">
<el-tooltip content="导入">
@@ -99,14 +75,15 @@
</el-tooltip>
<el-tooltip content="列表筛选">
<el-dropdown trigger="click">
<el-button type="default" icon="operation" circle />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="column in tableColumns" :key="column.prop" :command="column">
<el-checkbox v-model="column.show">{{ column.label }}</el-checkbox>
</el-dropdown-item>
</el-dropdown-menu>
</template>
<el-button type="default" icon="operation" circle />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="column in tableColumns" :key="column.prop"
:command="column">
<el-checkbox v-model="column.show">{{ column.label }}</el-checkbox>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-tooltip>
</div>
@@ -119,21 +96,21 @@
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection"
width="55" align="center" />
min-width="55" align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed
label="序号" width="60" />
label="序号" min-width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'name')?.show" key="name" label="任务名称"
prop="name" min-width="80" />
prop="name" min-width="140" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'func')?.show" key="func" label="执行函数"
prop="func" min-width="80" />
prop="func" min-width="140" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'trigger')?.show" key="trigger" label="触发器"
prop="trigger" min-width="80" />
prop="trigger" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'jobstore')?.show" key="jobstore"
label="存储器" prop="jobstore" min-width="80" />
label="存储器" prop="jobstore" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'executor')?.show" key="executor"
label="执行器" prop="executor" min-width="80" />
label="执行器" prop="executor" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'coalesce')?.show" key="coalesce"
label="并发执行" prop="coalesce" min-width="80">
label="并发执行" prop="coalesce" min-width="100">
<template #default="scope">
<el-tag :type="scope.row.coalesce === true ? 'success' : 'danger'">
{{ scope.row.coalesce === true ? "是" : "否" }}
@@ -141,7 +118,7 @@
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" key="status" label="状态"
prop="status" min-width="60">
prop="status" min-width="100">
<template #default="scope">
<el-tag :type="scope.row.status === true ? 'success' : 'danger'">
{{ scope.row.status === true ? "启用" : "停用" }}
@@ -151,12 +128,12 @@
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description"
label="描述" prop="description" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at"
label="创建时间" prop="created_at" min-width="120" sortable />
label="创建时间" prop="created_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at"
label="更新时间" prop="updated_at" min-width="120" sortable />
label="更新时间" prop="updated_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right"
label="操作" min-width="120">
label="操作" min-width="240">
<template #default="scope">
<el-button type="info" size="small" link icon="document"
@click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
@@ -164,16 +141,16 @@
@click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button type="danger" size="small" link icon="delete"
@click="handleOperation('delete', scope.row.id)">删除</el-button>
<el-dropdown>
<el-dropdown trigger="click">
<el-button type="default" size="small" link icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-menu @click="handleOption(scope.row, $event.key)">
<el-menu-item key="1">暂停</el-menu-item>
<el-menu-item key="2">恢复</el-menu-item>
<!-- <el-menu-item key="3">重启</el-menu-item> -->
</el-menu>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleOption(scope.row, 1)">暂停</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleOption(scope.row, 2)">恢复</el-dropdown-item>
<!-- <el-dropdown-item icon="CircleClose" @click="handleOption(scope.row, 3)">重启</el-dropdown-item> -->
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
@@ -205,9 +182,9 @@
<el-tag v-else type="danger"></el-tag>
</el-descriptions-item>
<el-descriptions-item label="最大实例数" :span="2">{{ detailFormData.max_instances
}}</el-descriptions-item>
}}</el-descriptions-item>
<el-descriptions-item label="触发器参数" :span="2">{{ detailFormData.trigger_args
}}</el-descriptions-item>
}}</el-descriptions-item>
<el-descriptions-item label="开始时间" :span="2">{{ detailFormData.start_date }}</el-descriptions-item>
<el-descriptions-item label="结束时间" :span="2">{{ detailFormData.end_date }}</el-descriptions-item>
<el-descriptions-item label="日志信息" :span="2">{{ detailFormData.message }}</el-descriptions-item>
@@ -217,7 +194,7 @@
</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">{{ detailFormData.description }}</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">{{ detailFormData.creator?.name
}}</el-descriptions-item>
}}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ detailFormData.created_at }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ detailFormData.updated_at }}</el-descriptions-item>
</el-descriptions>
@@ -338,13 +315,13 @@ defineOptions({
inheritAttrs: false,
});
import JobAPI, { JobTable, JobForm, JobPageQuery } from "@/api/system/job";
import JobAPI, { JobTable, JobForm, JobPageQuery } from "@/api/monitor/job";
import { ElMessage, ElMessageBox } from "element-plus";
import { useDebounceFn } from "@vueuse/core";
import IntervalTab from '@/components/IntervalTab/index.vue';
import 'vue3-cron-plus-picker/style.css'
import { Vue3CronPlusPicker } from 'vue3-cron-plus-picker'
import 'vue3-cron-plus-picker/style.css';
import { Vue3CronPlusPicker } from 'vue3-cron-plus-picker';
import { useDictStore } from "@/store/index";
const dictStore = useDictStore();
@@ -652,29 +629,6 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
}
}
// /
async function handleMoreClick(id: number) {
if (id && !selectIds.value.length) {
ElMessageBox.confirm("确认删除启用或停用该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await JobAPI.batchAvailableJob({ id: id ? id : selectIds.value });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessage.info('已取消批量操作');
});
}
}
const getOptions = async () => {
const dictOptions = await dictStore.getDict(['sys_job_group', 'sys_job_executor', 'sys_job_function', 'sys_job_trigger'])
return dictOptions
+7 -7
View File
@@ -65,12 +65,12 @@
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" width="55"
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" min-width="55"
align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号"
width="60" />
min-width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'session_id')?.show" key="session_id" label="会话编号"
prop="session_id" min-width="80" />
prop="session_id" min-width="250" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'ipaddr')?.show" key="ipaddr" label="IP地址"
prop="ipaddr" min-width="80" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'name')?.show" key="name" label="用户名" prop="name"
@@ -82,7 +82,7 @@
<el-table-column v-if="tableColumns.find(col => col.prop === 'os')?.show" key="os" label="操作系统" prop="os"
min-width="80" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'login_time')?.show" key="login_time" label="登录时间"
prop="login_time" min-width="80" />
prop="login_time" min-width="200" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" key="operation" fixed="right"
label="操作" min-width="60">
<template #default="scope">
@@ -186,8 +186,8 @@ async function handleSelectionChange(selection: any) {
}
// 强制退出
async function handleSubmit(id?: number) {
if (!id && !selectIds.value.length) {
async function handleSubmit(ids?: number[]) {
if (!ids && !selectIds.value.length) {
ElMessageBox.confirm("确认强制退出选中项?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
@@ -195,7 +195,7 @@ async function handleSubmit(id?: number) {
}).then(async () => {
try {
loading.value = true;
await OnlineAPI.deleteOnline({ id: id ? id : selectIds.value });
await OnlineAPI.deleteOnline({ ids });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
+88 -296
View File
@@ -11,14 +11,15 @@
<el-input v-model="queryFormData.config_key" placeholder="请输入配置键名" clearable />
</el-form-item>
<el-form-item prop="config_type" label="系统内置">
<el-select v-model="queryFormData.config_type" placeholder="请选择系统内置" clearable style="width: 160px">
<el-select v-model="queryFormData.config_type" placeholder="请选择系统内置" clearable>
<el-option value="true" label="是" />
<el-option value="false" label="否" />
</el-select>
</el-form-item>
<!-- 时间范围收起状态下隐藏 -->
<el-form-item v-if="isExpand" prop="start_time" label="创建时间">
<el-date-picker v-model="queryFormData.start_time" type="daterange" value-format="yyyy-MM-dd" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" />
<el-date-picker v-model="queryFormData.start_time" type="daterange" value-format="yyyy-MM-dd"
range-separator="" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
<el-form-item class="search-buttons">
<el-button type="primary" icon="search" @click="handleQuery">查询</el-button>
@@ -58,14 +59,12 @@
<div class="data-table__toolbar">
<div class="data-table__toolbar--actions">
<el-button type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleOperation('delete')">批量删除</el-button>
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0"
@click="handleDelete(selectIds)">批量删除</el-button>
</div>
<div class="data-table__toolbar--tools">
<el-tooltip content="导入">
<el-button type="info" icon="upload" circle @click="handleOperation('import')" />
</el-tooltip>
<el-tooltip content="导出">
<el-button type="warning" icon="download" circle @click="handleOperation('export')" />
<el-button type="warning" icon="download" circle @click="handleExport" />
</el-tooltip>
<el-tooltip content="刷新">
<el-button type="primary" icon="refresh" circle @click="handleRefresh" />
@@ -91,33 +90,52 @@
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" width="55" align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号" width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'config_name')?.show" key="config_name" label="配置名称" prop="config_name" min-width="80" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'config_key')?.show" key="config_key" label="配置键" prop="config_key" min-width="120" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'config_value')?.show" key="config_value" label="配置值" prop="config_value" min-width="100" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'config_type')?.show" key="config_type" label="系统内置" prop="config_type" min-width="60">
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" min-width="55"
align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号"
min-width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'config_name')?.show" key="config_name"
label="配置名称" prop="config_name" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'config_key')?.show" key="config_key" label="配置键"
prop="config_key" min-width="200" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'config_value')?.show" key="config_value"
label="配置值" prop="config_value" min-width="120" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'config_type')?.show" key="config_type"
label="系统内置" prop="config_type" min-width="100">
<template #default="scope">
<el-tag v-if="scope.row.config_type" type="success"></el-tag>
<el-tag v-else type="danger"></el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description" label="描述" prop="description" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间" prop="created_at" min-width="120" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at" label="更新时间" prop="updated_at" min-width="120" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作" min-width="120">
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description" label="描述"
prop="description" min-width="120" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'creator')?.show" key="creator" label="创建人"
min-width="120" sortable>
<template #default="scope">
<el-button type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button type="danger" size="small" link icon="delete" @click="handleOperation('delete', scope.row.id)">删除</el-button>
{{ scope.row.creator?.name }}
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间"
prop="created_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at" label="更新时间"
prop="updated_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作"
min-width="200">
<template #default="scope">
<el-button type="info" size="small" link icon="document"
@click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button type="primary" size="small" link icon="edit"
@click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button type="danger" size="small" link icon="delete"
@click="handleDelete([scope.row.id])">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size"
@pagination="loadingData" />
</template>
</el-card>
@@ -158,7 +176,8 @@
</el-radio-group>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit type="textarea" placeholder="请输入描述" />
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit type="textarea"
placeholder="请输入描述" />
</el-form-item>
</el-form>
</template>
@@ -173,86 +192,7 @@
</template>
</el-dialog>
<!-- 导出弹窗 -->
<el-dialog v-model="exportsModalVisible" :align-center="true" title="导出数据" width="600px" style="padding-right: 0"
@close="handleCloseExportsModal">
<!-- 滚动 -->
<el-scrollbar max-height="60vh">
<!-- 表单 -->
<el-form ref="exportsFormRef" style="padding-right: var(--el-dialog-padding-primary)" :model="exportsFormData"
:rules="exportsFormRules">
<el-form-item label="文件名" prop="filename">
<el-input v-model="exportsFormData.filename" clearable />
</el-form-item>
<el-form-item label="工作表名" prop="sheetname">
<el-input v-model="exportsFormData.sheetname" clearable />
</el-form-item>
<el-form-item label="数据源" prop="origin">
<el-select v-model="exportsFormData.origin">
<el-option label="当前数据 (当前页的数据)" :value="ExportsOriginEnum.CURRENT" />
<el-option label="选中数据 (所有选中的数据)" :value="ExportsOriginEnum.SELECTED"
:disabled="selectionData.length <= 0" />
<el-option label="全量数据 (所有分页的数据)" :value="ExportsOriginEnum.REMOTE"
:disabled="contentConfig.exportsAction === undefined" />
</el-select>
</el-form-item>
<el-form-item label="字段" prop="fields">
<el-checkbox-group v-model="exportsFormData.fields">
<template v-for="col in cols" :key="col.prop">
<el-checkbox v-if="col.prop" :value="col.prop" :label="col.label" />
</template>
</el-checkbox-group>
</el-form-item>
</el-form>
</el-scrollbar>
<!-- 弹窗底部操作按钮 -->
<template #footer>
<div style="padding-right: var(--el-dialog-padding-primary)">
<el-button type="primary" @click="handleExportsSubmit"> </el-button>
<el-button @click="handleCloseExportsModal"> </el-button>
</div>
</template>
</el-dialog>
<!-- 导入弹窗 -->
<el-dialog v-model="importModalVisible" :align-center="true" title="导入数据" width="600px" style="padding-right: 0"
@close="handleCloseImportModal">
<!-- 滚动 -->
<el-scrollbar max-height="60vh">
<!-- 表单 -->
<el-form ref="importFormRef" style="padding-right: var(--el-dialog-padding-primary)" :model="importFormData"
:rules="importFormRules">
<el-form-item label="文件名" prop="files">
<el-upload ref="uploadRef" v-model:file-list="importFormData.files" class="w-full"
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
:drag="true" :limit="1" :auto-upload="false" :on-exceed="handleFileExceed">
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
<span>将文件拖到此处</span>
<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
*.xlsx / *.xls
<el-link v-if="contentConfig.importTemplate" type="primary" icon="download" underline="never"
@click="handleDownloadTemplate">
下载模板
</el-link>
</div>
</template>
</el-upload>
</el-form-item>
</el-form>
</el-scrollbar>
<!-- 弹窗底部操作按钮 -->
<template #footer>
<div style="padding-right: var(--el-dialog-padding-primary)">
<el-button type="primary" :disabled="importFormData.files.length === 0" @click="handleImportSubmit">
</el-button>
<el-button @click="handleCloseImportModal"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
@@ -290,6 +230,7 @@ const tableColumns = ref([
{ prop: 'description', label: '描述', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
{ prop: 'creator', label: '创建人', show: true },
{ prop: 'operation', label: '操作', show: true }
])
@@ -315,7 +256,7 @@ const formData = reactive<ConfigForm>({
config_value: '',
config_type: false,
description: ''
})
});
// 弹窗状态
const dialogVisible = reactive({
@@ -440,9 +381,9 @@ async function handleSubmit() {
});
}
// 删除系统配置
async function handleOperation(type: 'delete' | 'import' | 'export', id?: number) {
if (type === 'delete' && !id && !selectIds.value.length) {
// 删除、批量删除
async function handleDelete(ids: number[]) {
if (!ids && !selectIds.value.length) {
ElMessageBox.confirm("确认删除该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
@@ -450,8 +391,7 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
}).then(async () => {
try {
loading.value = true;
await ConfigAPI.deleteConfig({ id: id ? id : selectIds.value });
ElMessage.success("删除成功");
await ConfigAPI.deleteConfig({ ids });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
@@ -462,198 +402,50 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
ElMessage.info('已取消删除');
});
}
else if (type === 'import') {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.xlsx, .xls';
input.click();
input.onchange = async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const formData = new FormData();
formData.append('file', file);
try {
loading.value = true;
await ConfigAPI.uploadFile(formData);
ElMessage.success('导入成功');
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
}
}
}
}
else if (type === 'export') {
ElMessageBox.confirm('是否确认导出当前系统配置?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
const response = await ConfigAPI.exportConfig(body);
const blob = new Blob([JSON.stringify(response.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = '系统配置.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
ElMessage.success('导出成功');
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessage.info('已取消导出');
});
}
else {
ElMessage.error('未知操作类型');
}
}
// 导出
function handleExport() {
const filename = exportsFormData.filename
? exportsFormData.filename
: props.contentConfig.permPrefix || "export";
const sheetname = exportsFormData.sheetname ? exportsFormData.sheetname : "sheet";
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet(sheetname);
const columns: Partial<ExcelJS.Column>[] = [];
cols.value.forEach((col) => {
if (col.label && col.prop && exportsFormData.fields.includes(col.prop)) {
columns.push({ header: col.label, key: col.prop });
async function handleExport() {
ElMessageBox.confirm('是否确认导出当前系统配置?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
const response = await ConfigAPI.exportConfig(body);
const blob = new Blob([JSON.stringify(response.data.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = '系统配置.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessage.info('已取消导出');
});
worksheet.columns = columns;
if (exportsFormData.origin === ExportsOriginEnum.REMOTE) {
if (props.contentConfig.exportsAction) {
props.contentConfig.exportsAction(lastFormData).then((res) => {
worksheet.addRows(res);
workbook.xlsx
.writeBuffer()
.then((buffer) => {
saveXlsx(buffer, filename as string);
})
.catch((error) => console.log(error));
});
} else {
ElMessage.error("未配置exportsAction");
}
} else {
worksheet.addRows(
exportsFormData.origin === ExportsOriginEnum.SELECTED ? selectionData.value : pageData.value
);
workbook.xlsx
.writeBuffer()
.then((buffer) => {
saveXlsx(buffer, filename as string);
})
.catch((error) => console.log(error));
}
}
// 下载导入模板
function handleDownloadTemplate() {
const importTemplate = props.contentConfig.importTemplate;
if (typeof importTemplate === "string") {
window.open(importTemplate);
} else if (typeof importTemplate === "function") {
importTemplate().then((response) => {
const fileData = response.data;
const fileName = decodeURI(
response.headers["content-disposition"].split(";")[1].split("=")[1]
);
saveXlsx(fileData, fileName);
});
} else {
ElMessage.error("未配置importTemplate");
}
}
// 导入
function handleImport() {
const importsAction = props.contentConfig.importsAction;
if (importsAction === undefined) {
ElMessage.error("未配置importsAction");
return;
}
// 获取选择的文件
const file = importFormData.files[0].raw as File;
// 创建Workbook实例
const workbook = new ExcelJS.Workbook();
// 使用FileReader对象来读取文件内容
const fileReader = new FileReader();
// 二进制字符串的形式加载文件
fileReader.readAsArrayBuffer(file);
fileReader.onload = (ev) => {
if (ev.target !== null && ev.target.result !== null) {
const result = ev.target.result as ArrayBuffer;
// 从 buffer中加载数据解析
workbook.xlsx
.load(result)
.then((workbook) => {
// 解析后的数据
const data = [];
// 获取第一个worksheet内容
const worksheet = workbook.getWorksheet(1);
if (worksheet) {
// 获取第一行的标题
const fields: any[] = [];
worksheet.getRow(1).eachCell((cell) => {
fields.push(cell.value);
});
// 遍历工作表的每一行(从第二行开始,因为第一行通常是标题行)
for (let rowNumber = 2; rowNumber <= worksheet.rowCount; rowNumber++) {
const rowData: IObject = {};
const row = worksheet.getRow(rowNumber);
// 遍历当前行的每个单元格
row.eachCell((cell, colNumber) => {
// 获取标题对应的键,并将当前单元格的值存储到相应的属性名中
rowData[fields[colNumber - 1]] = cell.value;
});
// 将当前行的数据对象添加到数组中
data.push(rowData);
}
}
if (data.length === 0) {
ElMessage.error("未解析到数据");
return;
}
importsAction(data).then(() => {
ElMessage.success("导入数据成功");
handleCloseImportModal();
handleRefresh(true);
});
})
.catch((error) => console.log(error));
} else {
ElMessage.error("读取文件失败");
}
};
}
onMounted(() => {
+26 -126
View File
@@ -8,7 +8,7 @@
<el-input v-model="queryFormData.name" placeholder="请输入部门名称" clearable />
</el-form-item>
<el-form-item prop="status" label="状态">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable style="width: 160px">
<el-select v-model="queryFormData.status" placeholder="请选择状态asdasda" clearable>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
@@ -58,39 +58,20 @@
<div class="data-table__toolbar--actions">
<el-button type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0"
@click="handleOperation('delete')">批量删除</el-button>
<el-dropdown>
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多
@click="handleDelete(selectIds)">批量删除</el-button>
<el-dropdown trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-menu @click="handleMoreClick">
<el-menu-item key="1">
<span>
<el-icon>
<Check />
</el-icon>
<span>批量启用</span>
</span>
</el-menu-item>
<el-menu-item key="2">
<span>
<el-icon>
<CircleClose />
</el-icon>
<span>批量停用</span>
</span>
</el-menu-item>
</el-menu>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="data-table__toolbar--tools">
<el-tooltip content="导入">
<el-button type="info" icon="upload" circle @click="handleOperation('import')" />
</el-tooltip>
<el-tooltip content="导出">
<el-button type="warning" icon="download" circle @click="handleOperation('export')" />
</el-tooltip>
<el-tooltip content="刷新">
<el-button type="primary" icon="refresh" circle @click="handleRefresh" />
</el-tooltip>
@@ -113,7 +94,7 @@
<el-table ref="dataTableRef" v-loading="loading" row-key="id" :data="pageTableData" :tree-props="{
children: 'children',
hasChildren: 'hasChildren',
}" highlight-current-row class="data-table__content" height="450" border stripe
}" highlight-current-row class="data-table__content" height="540" border stripe
@selection-change="handleSelectionChange">
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
@@ -123,9 +104,9 @@
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号"
width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'name')?.show" key="name" label="部门名称" prop="name"
min-width="80" />
min-width="120" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" key="status" label="状态"
prop="status" min-width="60">
prop="status" min-width="80">
<template #default="scope">
<el-tag :type="scope.row.status === true ? 'success' : 'danger'">
{{ scope.row.status ? "启用" : "停用" }}
@@ -133,7 +114,7 @@
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'order')?.show" key="order" label="排序" prop="order"
min-width="100" show-overflow-tooltip />
min-width="80" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description" label="描述"
prop="description" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间"
@@ -150,16 +131,11 @@
<el-button type="primary" size="small" link icon="edit"
@click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button type="danger" size="small" link icon="delete"
@click="handleOperation('delete', scope.row.id)">删除</el-button>
@click="handleDelete([scope.row.id])">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-if="total > 0" v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size"
@pagination="loadingData" />
</template>
</el-card>
<!-- 弹窗区域 -->
@@ -174,7 +150,6 @@
</el-descriptions-item>
<el-descriptions-item label="排序" :span="2">{{ detailFormData.order }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">{{ detailFormData.description }}</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">{{ detailFormData.creator?.name }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ detailFormData.created_at }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ detailFormData.updated_at }}</el-descriptions-item>
</el-descriptions>
@@ -262,8 +237,6 @@ const detailFormData = ref<DeptTable>({});
// 分页查询参数
const queryFormData = reactive<DeptPageQuery>({
page_no: 1,
page_size: 10,
name: undefined,
status: undefined,
start_time: undefined,
@@ -318,14 +291,12 @@ async function loadingData() {
// 查询(重置页码后获取数据)
async function handleQuery() {
queryFormData.page_no = 1;
loadingData();
}
// 重置查询
async function handleResetQuery() {
queryFormRef.value.resetFields();
queryFormData.page_no = 1;
loadingData();
}
@@ -351,13 +322,13 @@ async function handleCloseDialog() {
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
dialogVisible.type = type;
if (id) {
const data = await DeptAPI.getDeptDetail({ id });
const response = await DeptAPI.getDeptDetail({ id });
if (type === 'detail') {
dialogVisible.title = "部门详情";
Object.assign(detailFormData.value, data);
Object.assign(detailFormData.value, response.data.data);
} else if (type === 'update') {
dialogVisible.title = "修改部门";
Object.assign(formData, data);
Object.assign(formData, response.data.data);
}
} else {
dialogVisible.title = "新增部门";
@@ -376,7 +347,7 @@ async function handleSubmit() {
const id = formData.id;
if (id) {
try {
await DeptAPI.updateDept(formData)
await DeptAPI.updateDept({ id, ...formData })
dialogVisible.visible = false;
resetForm();
handleResetQuery();
@@ -401,9 +372,9 @@ async function handleSubmit() {
});
}
// 删除、导入、导出
async function handleOperation(type: 'delete' | 'import' | 'export', id?: number) {
if (type === 'delete' && !id && !selectIds.value.length) {
// 删除、批量删除
async function handleDelete(ids: number[]) {
if (!ids && !selectIds.value.length) {
ElMessageBox.confirm("确认删除该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
@@ -411,8 +382,7 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
}).then(async () => {
try {
loading.value = true;
await DeptAPI.deleteDept({ id: id ? id : selectIds.value });
ElMessage.success("删除成功");
await DeptAPI.deleteDept({ ids });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
@@ -423,89 +393,19 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
ElMessage.info('已取消删除');
});
}
else if (type === 'import') {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.xlsx, .xls';
input.click();
input.onchange = async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const formData = new FormData();
formData.append('file', file);
try {
loading.value = true;
await DeptAPI.uploadFile(formData);
ElMessage.success('导入成功');
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
}
}
}
}
else if (type === 'export') {
ElMessageBox.confirm('是否确认导出当前部门?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
const response = await DeptAPI.exportDept(body);
const blob = new Blob([JSON.stringify(response.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = '部门.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
ElMessage.success('导出成功');
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessage.info('已取消导出');
});
}
else {
ElMessage.error('未知操作类型');
}
}
// 批量启用/停用
async function handleMoreClick(id: number) {
if (id && !selectIds.value.length) {
ElMessageBox.confirm("确认删除启用或停用该项数据?", "警告", {
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await DeptAPI.batchAvailableDept({ id: id ? id : selectIds.value });
await DeptAPI.batchAvailableDept({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
@@ -1,17 +1,15 @@
<!-- 字典数据 -->
<template>
<div class="app-container">
<el-drawer v-model="drawerVisible" title="字典数据" size="70%">
<!-- 搜索区域 -->
<div class="search-container">
<el-form ref="queryFormRef" :model="queryFormData" :inline="true">
<el-form-item prop="dict_label" label="字典数据标签">
<el-input v-model="queryFormData.dict_label" placeholder="请输入字典标签" clearable />
</el-form-item>
<el-form-item prop="dict_type" label="字典数据类型">
<el-input v-model="queryFormData.dict_type" placeholder="请输入字典类型" clearable />
</el-form-item>
<el-form-item prop="status" label="状态">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable style="width: 160px">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
@@ -61,38 +59,22 @@
<div class="data-table__toolbar--actions">
<el-button type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0"
@click="handleOperation('delete')">批量删除</el-button>
<el-dropdown>
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多
@click="handleDelete(selectIds)">批量删除</el-button>
<el-dropdown trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-menu @click="handleMoreClick">
<el-menu-item key="1">
<span>
<el-icon>
<Check />
</el-icon>
<span>批量启用</span>
</span>
</el-menu-item>
<el-menu-item key="2">
<span>
<el-icon>
<CircleClose />
</el-icon>
<span>批量停用</span>
</span>
</el-menu-item>
</el-menu>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="data-table__toolbar--tools">
<el-tooltip content="导入">
<el-button type="info" icon="upload" circle @click="handleOperation('import')" />
</el-tooltip>
<el-tooltip content="导出">
<el-button type="warning" icon="download" circle @click="handleOperation('export')" />
<el-button type="warning" icon="download" circle @click="handleExport" />
</el-tooltip>
<el-tooltip content="刷新">
<el-button type="primary" icon="refresh" circle @click="handleRefresh" />
@@ -114,19 +96,19 @@
<!-- 表格区域系统配置列表 -->
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row
class="data-table__content" height="450" border stripe @selection-change="handleSelectionChange">
class="data-table__content" height="460" border stripe @selection-change="handleSelectionChange">
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" width="55"
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" min-width="55"
align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号"
width="60" />
min-width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'dict_label')?.show" key="dict_label" label="标签"
prop="dict_label" min-width="80" show-overflow-tooltip />
prop="dict_label" min-width="100" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" key="status" label="状态"
prop="status" min-width="60" show-overflow-tooltip>
prop="status" min-width="100" show-overflow-tooltip>
<template #default="scope">
<el-tag :type="scope.row.status === true ? 'success' : 'danger'">
{{ scope.row.status ? "启用" : "停用" }}
@@ -140,36 +122,42 @@
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'dict_value')?.show" key="dict_value" label="值"
prop="dict_value" min-width="60" show-overflow-tooltip />
prop="dict_value" min-width="100" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'css_class')?.show" key="css_class" label="样式属性"
prop="css_class" min-width="100" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'list_class')?.show" key="list_class" label="列表类样式"
prop="list_class" min-width="100" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'dict_sort')?.show" key="dict_sort" label="排序"
prop="dict_sort" min-width="40" />
prop="dict_sort" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'is_default')?.show" key="is_default" label="是否默认"
prop="is_default" min-width="60">
prop="is_default" min-width="100">
<template #default="scope">
<el-tag v-if="scope.row.is_default" type="success"></el-tag>
<el-tag v-else type="danger"></el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description" label="描述"
prop="description" min-width="80" />
prop="description" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'creator')?.show" label="创建人" prop="creator"
min-width="100">
<template #default="scope">
{{ scope.row.creator?.name }}
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间"
prop="created_at" min-width="120" sortable />
prop="created_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at" label="更新时间"
prop="updated_at" min-width="120" sortable />
prop="updated_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作"
min-width="120">
min-width="240">
<template #default="scope">
<el-button type="info" size="small" link icon="document"
@click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button type="primary" size="small" link icon="edit"
@click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button type="danger" size="small" link icon="delete"
@click="handleOperation('delete', scope.row.id)">删除</el-button>
@click="handleDelete([scope.row.id])">删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -222,7 +210,7 @@
<el-input v-model="formData.css_class" placeholder="请输入样式属性" :maxlength="50" />
</el-form-item>
<el-form-item label="列表类样式" prop="list_class">
<el-select v-model="formData.list_class" placeholder="请选择列表类样式" clearable style="width: 160px">
<el-select v-model="formData.list_class" placeholder="请选择列表类样式" clearable>
<el-option value="default" label="默认(default)" />
<el-option value="primary" label="主要(primary)" />
<el-option value="success" label="成功(success)" />
@@ -262,14 +250,11 @@
</template>
</el-dialog>
</div>
</el-drawer>
</template>
<script setup lang="ts">
defineOptions({
name: "DictData",
inheritAttrs: false,
});
import DictAPI, { DictDataTable, DictDataForm, DictDataPageQuery } from "@/api/system/dict";
import { ElMessage, ElMessageBox } from "element-plus";
@@ -284,6 +269,7 @@ const loading = ref(false);
const isExpand = ref(false);
const isExpandable = ref(true);
const drawerVisible = ref<boolean>(false);
//
const pageTableData = ref<DictDataTable[]>([]);
@@ -302,6 +288,7 @@ const tableColumns = ref([
{ prop: 'is_default', label: '是否默认', show: true },
{ prop: 'status', label: '状态', show: true },
{ prop: 'description', label: '描述', show: true },
{ prop: 'creator', label: '创建人', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
{ prop: 'operation', label: '操作', show: true }
@@ -436,7 +423,7 @@ async function handleSubmit() {
const id = formData.id;
if (id) {
try {
await DictAPI.updateDictData(formData)
await DictAPI.updateDictData({ id, ...formData })
dialogVisible.visible = false;
resetForm();
handleResetQuery();
@@ -461,9 +448,9 @@ async function handleSubmit() {
});
}
//
async function handleOperation(type: 'delete' | 'import' | 'export', id?: number) {
if (type === 'delete' && !id && !selectIds.value.length) {
//
async function handleDelete(ids: number[]) {
if (!ids && !selectIds.value.length) {
ElMessageBox.confirm("确认删除该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
@@ -471,8 +458,7 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
}).then(async () => {
try {
loading.value = true;
await DictAPI.deleteDictData({ id: id ? id : selectIds.value });
ElMessage.success("删除成功");
await DictAPI.deleteDictData({ ids });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
@@ -483,89 +469,63 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
ElMessage.info('已取消删除');
});
}
else if (type === 'import') {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.xlsx, .xls';
input.click();
}
input.onchange = async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const formData = new FormData();
formData.append('file', file);
try {
loading.value = true;
await DictAPI.uploadFile(formData);
ElMessage.success('导入成功');
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
//
async function handleExport() {
ElMessageBox.confirm('是否确认导出当前系统配置?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
const response = await DictAPI.exportDictData(body);
const blob = new Blob([JSON.stringify(response.data.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
//
const contentDisposition = response.headers['content-disposition'];
let fileName = '字典数据.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}
else if (type === 'export') {
ElMessageBox.confirm('是否确认导出当前系统配置?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
const response = await DictAPI.exportDictData(body);
const blob = new Blob([JSON.stringify(response.data.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
//
const contentDisposition = response.headers['content-disposition'];
let fileName = '系统配置.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
ElMessage.success('导出成功');
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessage.info('已取消导出');
});
}
else {
ElMessage.error('未知操作类型');
}
}).catch(() => {
ElMessage.info('已取消导出');
});
}
// /
async function handleMoreClick(id: number) {
if (id && !selectIds.value.length) {
ElMessageBox.confirm("确认删除启用或停用该项数据?", "警告", {
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await DictAPI.batchUpdateDictData({ id: id ? id : selectIds.value });
await DictAPI.batchAvailableDictData({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
@@ -578,6 +538,17 @@ async function handleMoreClick(id: number) {
}
}
//
watch(
() => props.modelValue,
(visible) => {
if (visible && props.dictType) {
queryFormData.dict_type = props.dictType;
loadingData();
}
}
);
onMounted(() => {
loadingData();
});
+95 -119
View File
@@ -11,7 +11,7 @@
<el-input v-model="queryFormData.dict_type" placeholder="请输入字典类型" clearable />
</el-form-item>
<el-form-item prop="status" label="状态">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable style="width: 160px">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
@@ -61,38 +61,22 @@
<div class="data-table__toolbar--actions">
<el-button type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0"
@click="handleOperation('delete')">批量删除</el-button>
<el-dropdown>
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多
@click="handleDelete(selectIds)">批量删除</el-button>
<el-dropdown trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-menu @click="handleMoreClick">
<el-menu-item key="1">
<span>
<el-icon>
<Check />
</el-icon>
<span>批量启用</span>
</span>
</el-menu-item>
<el-menu-item key="2">
<span>
<el-icon>
<CircleClose />
</el-icon>
<span>批量停用</span>
</span>
</el-menu-item>
</el-menu>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="data-table__toolbar--tools">
<el-tooltip content="导入">
<el-button type="info" icon="upload" circle @click="handleOperation('import')" />
</el-tooltip>
<el-tooltip content="导出">
<el-button type="warning" icon="download" circle @click="handleOperation('export')" />
<el-button type="warning" icon="download" circle @click="handleExport" />
</el-tooltip>
<el-tooltip content="刷新">
<el-button type="primary" icon="refresh" circle @click="handleRefresh" />
@@ -118,19 +102,14 @@
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" width="55"
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" min-width="55"
align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号"
width="60" />
min-width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'dict_name')?.show" key="dict_name" label="字典名称"
prop="dict_name" min-width="80" />
prop="dict_name" min-width="120" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'dict_type')?.show" key="dict_type" label="字典类型"
prop="dict_type" min-width="120">
<template #default="scope">
<router-link :to="'/system/dict_data?dict_type=' + scope.row.dict_type">
<el-tag type="primary">{{ scope.row.dict_type }}</el-tag>
</router-link>
</template>
prop="dict_type" min-width="160">
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" key="status" label="状态"
prop="status" min-width="60">
@@ -141,21 +120,29 @@
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description" label="描述"
prop="description" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间"
prop="created_at" min-width="120" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at" label="更新时间"
prop="updated_at" min-width="120" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作"
prop="description" min-width="140" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'creator')?.show" label="创建人" prop="creator"
min-width="120">
<template #default="scope">
{{ scope.row.creator?.name }}
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间"
prop="created_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at" label="更新时间"
prop="updated_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作"
min-width="260">
<template #default="scope">
<el-button type="warning" size="small" link icon="document"
@click="handleDictDataDrawer(scope.row.dict_type)">字典</el-button>
<el-button type="info" size="small" link icon="document"
@click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button type="primary" size="small" link icon="edit"
@click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button type="danger" size="small" link icon="delete"
@click="handleOperation('delete', scope.row.id)">删除</el-button>
@click="handleDelete([scope.row.id])">删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -218,6 +205,8 @@
</template>
</el-dialog>
<DataDrawer v-if="drawerVisible" v-model="drawerVisible" :dict-type="currentDictType" />
</div>
</template>
@@ -230,6 +219,7 @@ defineOptions({
import DictAPI, { DictTable, DictForm, DictPageQuery } from "@/api/system/dict";
import { ElMessage, ElMessageBox } from "element-plus";
import { useDebounceFn } from "@vueuse/core";
import DataDrawer from "@/views/system/dict/components/DataDrawer.vue"
const queryFormRef = ref();
const dataFormRef = ref();
@@ -252,6 +242,7 @@ const tableColumns = ref([
{ prop: 'dict_type', label: '字典类型', show: true },
{ prop: 'status', label: '状态', show: true },
{ prop: 'description', label: '描述', show: true },
{ prop: 'creator', label: '创建人', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
{ prop: 'operation', label: '操作', show: true }
@@ -300,6 +291,13 @@ const handleRefresh = useDebounceFn(() => {
ElMessage.success("刷新成功");
}, 1000);
// 抽屉显隐
const drawerVisible = ref(false);
// 添加字典类型变量
const currentDictType = ref('');
// 加载表格数据
async function loadingData() {
loading.value = true;
@@ -376,7 +374,7 @@ async function handleSubmit() {
const id = formData.id;
if (id) {
try {
await DictAPI.updateDictType(formData)
await DictAPI.updateDictType({ id, ...formData })
dialogVisible.visible = false;
resetForm();
handleResetQuery();
@@ -401,9 +399,9 @@ async function handleSubmit() {
});
}
// 删除、导入、导出
async function handleOperation(type: 'delete' | 'import' | 'export', id?: number) {
if (type === 'delete' && !id && !selectIds.value.length) {
// 删除、批量删除
async function handleDelete(ids: number[]) {
if (!ids && !selectIds.value.length) {
ElMessageBox.confirm("确认删除该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
@@ -411,8 +409,7 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
}).then(async () => {
try {
loading.value = true;
await DictAPI.deleteDictType({ id: id ? id : selectIds.value });
ElMessage.success("删除成功");
await DictAPI.deleteDictType({ ids });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
@@ -423,89 +420,63 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
ElMessage.info('已取消删除');
});
}
else if (type === 'import') {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.xlsx, .xls';
input.click();
}
input.onchange = async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const formData = new FormData();
formData.append('file', file);
try {
loading.value = true;
await DictAPI.uploadFile(formData);
ElMessage.success('导入成功');
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
// 导出
async function handleExport() {
ElMessageBox.confirm('是否确认导出当前系统配置?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
const response = await DictAPI.exportDictType(body);
const blob = new Blob([JSON.stringify(response.data.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = '字典类型.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}
else if (type === 'export') {
ElMessageBox.confirm('是否确认导出当前系统配置?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
const response = await DictAPI.exportDictType(body);
const blob = new Blob([JSON.stringify(response.data.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = '系统配置.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
ElMessage.success('导出成功');
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessage.info('已取消导出');
});
}
else {
ElMessage.error('未知操作类型');
}
}).catch(() => {
ElMessage.info('已取消导出');
});
}
// 批量启用/停用
async function handleMoreClick(id: number) {
if (id && !selectIds.value.length) {
ElMessageBox.confirm("确认删除启用或停用该项数据?", "警告", {
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await DictAPI.batchAvailableDictType({ id: id ? id : selectIds.value });
await DictAPI.batchAvailableDict({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
@@ -518,6 +489,11 @@ async function handleMoreClick(id: number) {
}
}
function handleDictDataDrawer(dictType: string) {
currentDictType.value = dictType;
drawerVisible.value = true;
}
onMounted(() => {
loadingData();
});
+114 -86
View File
@@ -54,15 +54,27 @@
<div class="data-table__toolbar">
<div class="data-table__toolbar--actions">
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0"
@click="handleOperation('delete')">批量删除</el-button>
@click="handleDelete(selectIds)">批量删除</el-button>
</div>
<div class="data-table__toolbar--tools">
<el-tooltip content="导出">
<el-button type="warning" icon="download" circle @click="handleOperation('export')" />
<el-button type="warning" icon="download" circle @click="handleExport" />
</el-tooltip>
<el-tooltip content="刷新">
<el-button type="primary" icon="refresh" circle @click="handleRefresh" />
</el-tooltip>
<el-tooltip content="列表筛选">
<el-dropdown trigger="click">
<el-button type="default" icon="operation" circle />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="column in tableColumns" :key="column.prop" :command="column">
<el-checkbox v-model="column.show">{{ column.label }}</el-checkbox>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-tooltip>
</div>
</div>
@@ -72,44 +84,57 @@
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column prop='selection' type="selection" width="55" align="center" />
<el-table-column type="index" fixed label="序号" width="60" />
<el-table-column label="请求路径" prop="request_path" min-width="200" show-overflow-tooltip />
<el-table-column label="请求方法" prop="request_method" min-width="80">
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" prop='selection'
type="selection" min-width="55" align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号"
min-width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'request_path')?.show" label="请求路径"
prop="request_path" min-width="200" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'request_method')?.show" label="请求方法"
prop="request_method" min-width="100">
<template #default="scope">
<el-tag :type="getMethodType(scope.row.request_method)">
{{ scope.row.request_method }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="状态码" prop="response_code" min-width="60">
<el-table-column v-if="tableColumns.find(col => col.prop === 'response_code')?.show" label="状态码"
prop="response_code" min-width="100">
<template #default="scope">
<el-tag :type="getStatusCodeType(scope.row.response_code)">
{{ scope.row.response_code }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="请求IP" min-width="90">
<el-table-column v-if="tableColumns.find(col => col.prop === 'request_ip')?.show" label="请求IP" prop="request_ip"
min-width="150">
<template #default="scope">
<el-text>{{ scope.row.request_ip }}</el-text>
<CopyButton v-if="scope.row.request_ip" :text="scope.row.request_ip" style="margin-left: 2px" />
</template>
</el-table-column>
<el-table-column label="浏览器" prop="request_browser" min-width="60" />
<el-table-column label="系统" prop="request_os" min-width="80" />
<el-table-column label="描述" prop="description" min-width="100" show-overflow-tooltip />
<el-table-column label="创建时间" prop="created_at" min-width="120" sortable />
<el-table-column label="创建人" min-width="100">
<el-table-column v-if="tableColumns.find(col => col.prop === 'process_time')?.show" label="处理时间"
prop="process_time" min-width="200" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'request_browser')?.show" label="浏览器"
prop="request_browser" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'request_os')?.show" label="系统" prop="request_os"
min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" label="描述" prop="description"
min-width="120" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" label="创建时间" prop="created_at"
min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'creator')?.show" label="创建人" prop="creator"
min-width="120">
<template #default="scope">
{{ scope.row.creator?.name }}
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" min-width="100">
<el-table-column label="操作" fixed="right" min-width="150">
<template #default="scope">
<el-button type="primary" size="small" link icon="document"
@click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button type="danger" size="small" link icon="delete"
@click="handleOperation('delete', scope.row.id)">删除</el-button>
@click="handleDelete([scope.row.id])">删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -143,9 +168,9 @@
class="long-text-editor" />
</el-descriptions-item>
<el-descriptions-item label="响应数据" :span="4">
<el-input v-model="detailFormData.response_json" type="textarea" :rows="5" readonly
class="long-text-editor" />
<!-- <el-scrollbar max-height="72vh">
<!-- <el-input v-model="detailFormData.response_json" type="textarea" :rows="5" readonly
class="long-text-editor" /> -->
<el-scrollbar max-height="72vh">
<div class="absolute z-36 right-5 top-2">
<el-link type="primary" @click="handleCopyCode">
<el-icon>
@@ -153,19 +178,11 @@
</el-icon>
一键复制
</el-link>
</div> -->
</div>
<!-- <Codemirror
ref="cmRef"
v-model:value="code"
placeholder="请输入代码"
:options="cmOptions"
border
:readonly="true"
:width="700"
:height="100"
/> -->
<!-- </el-scrollbar> -->
<Codemirror ref="cmRef" v-model:value="code" placeholder="请输入代码" :options="cmOptions" border
:readonly="true" :width="700" :height="100" />
</el-scrollbar>
</el-descriptions-item>
<el-descriptions-item label="处理时间" :span="2">{{ detailFormData.process_time }}</el-descriptions-item>
<el-descriptions-item label="浏览器" :span="2">{{ detailFormData.request_browser }}</el-descriptions-item>
@@ -210,13 +227,31 @@ const dataFormRef = ref();
const total = ref(0);
const selectIds = ref<number[]>([]);
const loading = ref(false);
const isExpand = ref(false);
const isExpandable = ref(true);
// 分页表单
const pageTableData = ref<LogTable[]>([]);
// 表格列配置
const tableColumns = ref([
{ prop: 'selection', label: '选择框', show: true },
{ prop: 'index', label: '序号', show: true },
{ prop: 'request_path', label: '请求路径', show: true },
{ prop: 'request_method', label: '请求方法', show: true },
{ prop: 'response_code', label: '响应状态码', show: true },
{ prop: 'request_ip', label: '请求IP', show: true },
{ prop: 'process_time', label: '处理时间', show: true },
{ prop: 'request_browser', label: '浏览器', show: true },
{ prop: 'request_os', label: '操作系统', show: true },
{ prop: 'login_location', label: '登录地点', show: true },
{ prop: 'description', label: '描述', show: true },
{ prop: 'creator', label: '创建人', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
{ prop: 'operation', label: '操作', show: true }
])
// 详情表单
const detailFormData = ref<LogTable>({});
@@ -237,14 +272,12 @@ const dialogVisible = reactive({
type: 'create' as 'create' | 'update' | 'detail',
});
// 刷新数据(防抖)
const handleRefresh = useDebounceFn(() => {
loadingData();
ElMessage.success("刷新成功");
}, 1000);
const getStatusCodeType = (code?: number) => {
if (code === undefined) {
return 'info';
@@ -324,8 +357,8 @@ async function handleCloseDialog() {
resetForm();
}
// 打开系统配置弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
// 打开日志详情弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id: number) {
dialogVisible.type = type;
if (id) {
const response = await LogAPI.getLogDetail({ id });
@@ -338,10 +371,9 @@ async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: numbe
dialogVisible.visible = true;
}
// 删除、导出
async function handleOperation(type: 'delete' | 'export', id?: number) {
if (type === 'delete' && !id && !selectIds.value.length) {
// 删除、批量删除
async function handleDelete(ids: number[]) {
if (!ids && !selectIds.value.length) {
ElMessageBox.confirm("确认删除该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
@@ -349,8 +381,7 @@ async function handleOperation(type: 'delete' | 'export', id?: number) {
}).then(async () => {
try {
loading.value = true;
await LogAPI.deleteLog({ id: id ? id : selectIds.value });
ElMessage.success("删除成功");
await LogAPI.deleteLog({ ids });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
@@ -361,57 +392,54 @@ async function handleOperation(type: 'delete' | 'export', id?: number) {
ElMessage.info('已取消删除');
});
}
else if (type === 'export') {
ElMessageBox.confirm('是否确认导出日志?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
}
const response = await LogAPI.exportLog(body);
const blob = new Blob([JSON.stringify(response.data.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = '系统配置.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
// 导出
async function handleExport() {
ElMessageBox.confirm('是否确认导出当前系统配置?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
const response = await LogAPI.exportLog(body);
const blob = new Blob([JSON.stringify(response.data.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = '系统配置.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
ElMessage.success('导出成功');
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessage.info('已取消导出');
});
}
else {
ElMessage.error('未知操作类型');
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessage.info('已取消导出');
});
}
const cmOptions: EditorConfiguration = {
// mode: "text/javascript",
mode: "text/json",
mode: "text/javascript",
};
const code = ref();
const cmRef = ref<CmComponentRef>();
+57 -187
View File
@@ -8,7 +8,7 @@
<el-input v-model="queryFormData.name" placeholder="请输入标题" clearable />
</el-form-item>
<el-form-item prop="status" label="状态">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable style="width: 160px">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
@@ -45,10 +45,10 @@
<template #header>
<div class="card-header">
<span>
<el-tooltip content="公告通知管理维护系统的公告和通知。">
<el-tooltip content="菜单管理系统的菜单和权限。">
<QuestionFilled class="w-4 h-4 mx-1" />
</el-tooltip>
公告通知列表
菜单列表
</span>
</div>
</template>
@@ -58,53 +58,22 @@
<div class="data-table__toolbar--actions">
<el-button type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0"
@click="handleOperation('delete')">批量删除</el-button>
<el-dropdown>
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多
</el-button>
<template #dropdown>
<el-menu @click="handleMoreClick">
<el-menu-item key="1">
<span>
<el-icon>
<Check />
</el-icon>
<span>批量启用</span>
</span>
</el-menu-item>
<el-menu-item key="2">
<span>
<el-icon>
<CircleClose />
</el-icon>
<span>批量停用</span>
</span>
</el-menu-item>
</el-menu>
</template>
</el-dropdown>
@click="handleDelete(selectIds)">批量删除</el-button>
<el-dropdown trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="data-table__toolbar--tools">
<el-tooltip content="导入">
<el-button type="info" icon="upload" circle @click="handleOperation('import')" />
</el-tooltip>
<el-tooltip content="导出">
<el-button type="warning" icon="download" circle @click="handleOperation('export')" />
</el-tooltip>
<el-tooltip content="刷新">
<el-button type="primary" icon="refresh" circle @click="handleRefresh" />
</el-tooltip>
<el-tooltip content="列表筛选">
<el-dropdown trigger="click">
<el-button type="default" icon="operation" circle />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="column in tableColumns" :key="column.prop" :command="column">
<el-checkbox v-model="column.show">{{ column.label }}</el-checkbox>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button type="default" icon="refresh" circle @click="handleRefresh" />
</el-tooltip>
</div>
</div>
@@ -118,19 +87,15 @@
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" width="55"
align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号"
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" fixed label="序号"
width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'name')?.show" key="name" label="菜单名称" prop="name"
min-width="120">
<el-table-column label="菜单名称" prop="name" min-width="120">
<template #default="scope">
{{ scope.row.name }}
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'icon')?.show" key="icon" label="图标" prop="icon"
min-width="80" align="center">
<el-table-column label="图标" prop="icon" min-width="80" align="center">
<template #default="scope">
<template v-if="scope.row.icon && scope.row.icon.startsWith('el-icon')">
<el-icon style="vertical-align: -0.15em">
@@ -142,16 +107,14 @@
</template>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" key="status" label="状态"
prop="status" min-width="80" align="center">
<el-table-column label="状态" prop="status" min-width="80" align="center">
<template #default="scope">
<el-tag :type="scope.row.status === true ? 'success' : 'danger'">
{{ scope.row.status ? "启用" : "停用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'type')?.show" key="type" label="类型" prop="type"
min-width="80" align="center">
<el-table-column label="类型" prop="type" min-width="80" align="center">
<template #default="scope">
<el-tag v-if="scope.row.type === MenuTypeEnum.CATALOG" type="warning">目录</el-tag>
<el-tag v-if="scope.row.type === MenuTypeEnum.MENU" type="success">菜单</el-tag>
@@ -159,46 +122,33 @@
<el-tag v-if="scope.row.type === MenuTypeEnum.EXTLINK" type="info">外链</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'order')?.show" key="order" label="排序" prop="order"
min-width="80" align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'permission')?.show" key="permission" label="权限标识"
prop="permission" min-width="100" align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'router_name')?.show" key="router_name"
label="路由名称" prop="router_name" min-width="80" align="left" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'route_path')?.show" key="route_path" label="路由路径"
prop="router_name" min-width="80" align="left" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'component_path')?.show" key="component_path"
label="组件路径" prop="component_path" min-width="80" align="left" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'redirect')?.show" key="redirect" label="重定向"
prop="redirect" min-width="80" align="left" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'keep_alive')?.show" key="keep_alive" label="是否缓存"
prop="keep_alive" min-width="80" align="center">
<el-table-column label="排序" prop="order" min-width="80" />
<el-table-column label="权限标识" prop="permission" min-width="100" />
<el-table-column label="路由名称" prop="router_name" min-width="100" />
<el-table-column label="路由路径" prop="router_name" min-width="100" />
<el-table-column label="组件路径" prop="component_path" min-width="100" />
<el-table-column label="重定向" prop="redirect" min-width="100" />
<el-table-column label="是否缓存" prop="keep_alive" min-width="100">
<template #default="scope">
<el-tag v-if="scope.row.keep_alive" type="success"></el-tag>
<el-tag v-else type="danger"></el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'hidden')?.show" key="hidden" label="是否隐藏"
prop="hidden" min-width="80" align="center">
<el-table-column label="是否隐藏" prop="hidden" min-width="100">
<template #default="scope">
<el-tag v-if="scope.row.hidden" type="success"></el-tag>
<el-tag v-else type="danger"></el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'always_show')?.show" key="always_show"
label="显示根路由" prop="always_show" min-width="80" align="center">
<el-table-column label="显示根路由" prop="always_show" min-width="120">
<template #default="scope">
<el-tag v-if="scope.row.always_show" type="success"></el-tag>
<el-tag v-else type="danger"></el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'title')?.show" key="title" label="菜单标题"
prop="title" min-width="80" align="left" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'params')?.show" key="params" label="路由参数"
prop="params" min-width="80" align="left" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'affix')?.show" key="affix" label="固定路由"
prop="affix" min-width="80" align="center">
<el-table-column label="菜单标题" prop="title" min-width="100"/>
<el-table-column label="路由参数" prop="params" min-width="100" />
<el-table-column label="固定路由" prop="affix" min-width="100">
<template #default="scope">
<el-tag :type="scope.row.affix ? 'success' : 'danger'">
{{ scope.row.affix ? '是' : '否' }}
@@ -206,15 +156,11 @@
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description" label="描述"
prop="description" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间"
prop="created_at" min-width="120" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at" label="更新时间"
prop="updated_at" min-width="120" sortable />
<el-table-column label="描述" prop="description" min-width="100" />
<el-table-column label="创建时间" prop="created_at" min-width="200" sortable />
<el-table-column label="更新时间" prop="updated_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" align="center"
label="操作" min-width="220">
<el-table-column fixed="right" align="center" label="操作" min-width="260">
<template #default="scope">
<el-button v-if="scope.row.type == MenuTypeEnum.CATALOG || scope.row.type == MenuTypeEnum.MENU"
type="primary" link size="small" icon="plus" @click.stop="handleOpenDialog(scope.row.id)">
@@ -226,15 +172,15 @@
<el-button type="primary" size="small" link icon="edit"
@click.stop="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button type="danger" size="small" link icon="delete"
@click.stop="handleOperation('delete', scope.row.id)">删除</el-button>
@click.stop="handleDelete([scope.row.id])">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-if="total > 0" v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size"
@pagination="loadingData" />
<pagination v-if="total > 0" v-model:total="total" v-model:page="queryFormData.page_no"
v-model:limit="queryFormData.page_size" @pagination="loadingData" />
</template>
</el-card>
@@ -518,34 +464,6 @@ const isExpandable = ref(true);
// 分页表单
const pageTableData = ref<MenuTable[]>([]);
// 表格列配置
const tableColumns = ref([
{ prop: 'selection', label: '选择框', show: true },
{ prop: 'index', label: '序号', show: true },
{ prop: 'name', label: '标题', show: true },
{ prop: 'type', label: '类型', show: true },
{ prop: 'icon', label: '图标', show: true },
{ prop: 'order', label: '排序', show: true },
{ prop: 'permission', label: '权限标识', show: true },
{ prop: 'route_name', label: '路由名称', show: true },
{ prop: 'route_path', label: '路由路径', show: true },
{ prop: 'component_path', label: '组件路径', show: true },
{ prop: 'redirect', label: '跳转路由', show: true },
{ prop: 'parent_id', label: '父级菜单', show: true },
{ prop: 'parent_name', label: '父级菜单名称', show: true },
{ prop: 'keep_alive', label: '缓存页面', show: true },
{ prop: 'hidden', label: '显示状态', show: true },
{ prop: 'always_show', label: '始终显示', show: true },
{ prop: 'title', label: '菜单标题', show: true },
{ prop: 'params', label: '路由参数', show: true },
{ prop: 'affix', label: '固定路由', show: true },
{ prop: 'status', label: '状态', show: true },
{ prop: 'description', label: '描述', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
{ prop: 'operation', label: '操作', show: true }
])
// 详情表单
const detailFormData = ref<MenuTable>({});
@@ -736,9 +654,9 @@ async function handleSubmit() {
});
}
// 删除、导入、导出
async function handleOperation(type: 'delete' | 'import' | 'export', id?: number) {
if (type === 'delete' && !id && !selectIds.value.length) {
// 删除、批量删除
async function handleDelete(ids: number[]) {
if (!ids && !selectIds.value.length) {
ElMessageBox.confirm("确认删除该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
@@ -746,8 +664,7 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
}).then(async () => {
try {
loading.value = true;
await MenuAPI.deleteMenu({ id: id ? id : selectIds.value });
ElMessage.success("删除成功");
await DictAPI.deleteDictType({ ids });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
@@ -758,76 +675,29 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
ElMessage.info('已取消删除');
});
}
else if (type === 'import') {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.xlsx, .xls';
input.click();
}
input.onchange = async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const formData = new FormData();
formData.append('file', file);
try {
loading.value = true;
await MenuAPI.uploadFile(formData);
ElMessage.success('导入成功');
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
}
}
}
}
else if (type === 'export') {
ElMessageBox.confirm('是否确认导出当前系统配置?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
// 批量启用/停用
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
const response = await MenuAPI.exportMenu(body);
const blob = new Blob([JSON.stringify(response.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = '系统配置.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
ElMessage.success('导出成功');
await MenuAPI.batchAvailableMenu({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
ElMessage.error(error.message);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessage.info('已取消导出');
ElMessage.info('已取消批量操作');
});
}
else {
ElMessage.error('未知操作类型');
}
}
onMounted(() => {
+347 -137
View File
@@ -11,19 +11,24 @@
<el-input v-model="queryFormData.notice_type" placeholder="请输入类型" clearable />
</el-form-item>
<el-form-item prop="status" label="状态">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable style="width: 160px">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
</el-form-item>
<!-- 时间范围收起状态下隐藏 -->
<el-form-item v-if="isExpand" prop="start_time" label="创建时间">
<el-date-picker v-model="queryFormData.start_time" type="daterange" value-format="yyyy-MM-dd" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" />
<el-date-picker v-model="queryFormData.start_time" type="daterange" value-format="yyyy-MM-dd"
range-separator="" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
<!-- 查询重置展开/收起按钮 -->
<el-form-item class="search-buttons">
<el-button type="primary" icon="search" @click="handleQuery">查询</el-button>
<el-button icon="refresh" @click="handleResetQuery">重置</el-button>
<el-button type="primary" icon="search" @click="handleQuery">
查询
</el-button>
<el-button icon="refresh" @click="handleResetQuery">
重置
</el-button>
<!-- 展开/收起 -->
<template v-if="isExpandable">
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
@@ -58,39 +63,27 @@
<!-- 功能区域 -->
<div class="data-table__toolbar">
<div class="data-table__toolbar--actions">
<el-button type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleOperation('delete')">批量删除</el-button>
<el-dropdown>
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多
<el-button type="success" icon="plus" @click="handleOpenDialog('create')">
新增
</el-button>
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">
批量删除
</el-button>
<el-dropdown trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-menu @click="handleMoreClick">
<el-menu-item key="1">
<span>
<el-icon>
<Check />
</el-icon>
<span>批量启用</span>
</span>
</el-menu-item>
<el-menu-item key="2">
<span>
<el-icon>
<CircleClose />
</el-icon>
<span>批量停用</span>
</span>
</el-menu-item>
</el-menu>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="data-table__toolbar--tools">
<el-tooltip content="导入">
<el-button type="info" icon="upload" circle @click="handleOperation('import')" />
</el-tooltip>
<el-tooltip content="导出">
<el-button type="warning" icon="download" circle @click="handleOperation('export')" />
<el-button type="warning" icon="download" circle @click="handleExport" />
</el-tooltip>
<el-tooltip content="刷新">
<el-button type="primary" icon="refresh" circle @click="handleRefresh" />
@@ -101,7 +94,9 @@
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="column in tableColumns" :key="column.prop" :command="column">
<el-checkbox v-model="column.show">{{ column.label }}</el-checkbox>
<el-checkbox v-model="column.show">
{{ column.label }}
</el-checkbox>
</el-dropdown-item>
</el-dropdown-menu>
</template>
@@ -111,53 +106,73 @@
</div>
<!-- 表格区域系统配置列表 -->
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row class="data-table__content" height="450" border stripe @selection-change="handleSelectionChange">
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row
class="data-table__content" height="450" border stripe @selection-change="handleSelectionChange">
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" width="55" align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号" width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'notice_title')?.show" label="通知标题" prop="notice_title" min-width="80" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" label="状态" prop="status" min-width="60">
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" min-width="55"
align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号"
min-width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'notice_title')?.show" label="通知标题"
prop="notice_title" min-width="140" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" label="状态" prop="status"
min-width="100">
<template #default="scope">
<el-tag :type="scope.row.status === true ? 'success' : 'danger'">
{{ scope.row.status === true ? "启用" : "停用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'notice_content')?.show" label="通知内容" prop="notice_content" min-width="100" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'notice_type')?.show" label="通知类型" prop="notice_type" min-width="60">
<el-table-column v-if="tableColumns.find(col => col.prop === 'notice_content')?.show" label="通知内容"
prop="notice_content" min-width="100" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'notice_type')?.show" label="通知类型"
prop="notice_type" min-width="100">
<template #default="scope">
<el-tag :type="scope.row.notice_type === '1' ? 'primary' : 'warning'">
{{ scope.row.notice_type === '1' ? "通知" : "公告" }}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" label="描述" prop="description" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" label="创建时间" prop="created_at" min-width="120" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" label="更新时间" prop="updated_at" min-width="120" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作" min-width="120">
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" label="描述" prop="description"
min-width="140" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" label="创建时间" prop="created_at"
min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" label="更新时间" prop="updated_at"
min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作"
min-width="200">
<template #default="scope">
<el-button type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button type="danger" size="small" link icon="delete" @click="handleOperation('delete', scope.row.id)">删除</el-button>
<el-button type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">
详情
</el-button>
<el-button type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">
编辑
</el-button>
<el-button type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size"
@pagination="loadingData" />
</template>
</el-card>
<!-- 弹窗区域 -->
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" top="3vh" @close="handleCloseDialog">
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" top="3vh" @close="handleCloseDialog">
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="4" border>
<el-descriptions-item label="标题" :span="2">{{ detailFormData.notice_title }}</el-descriptions-item>
<el-descriptions-item label="标题" :span="2">
{{ detailFormData.notice_title }}
</el-descriptions-item>
<el-descriptions-item label="类型" :span="2">
<el-tag :type="detailFormData.notice_type === '1' ? 'primary' : 'warning'">
{{ detailFormData.notice_type === '1' ? '通知' : '公告' }}
@@ -168,11 +183,21 @@
{{ detailFormData.status ? '启用' : '停用' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="内容" :span="2">{{ detailFormData.notice_content }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">{{ detailFormData.description }}</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">{{ detailFormData.creator?.name }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ detailFormData.created_at }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ detailFormData.updated_at }}</el-descriptions-item>
<el-descriptions-item label="内容" :span="2">
{{ detailFormData.notice_content }}
</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">
{{ detailFormData.description }}
</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">
{{ detailFormData.creator?.name }}
</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">
{{ detailFormData.created_at }}
</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">
{{ detailFormData.updated_at }}
</el-descriptions-item>
</el-descriptions>
</template>
<!-- 新增编辑表单 -->
@@ -189,8 +214,12 @@
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :value="true">启用</el-radio>
<el-radio :value="false">停用</el-radio>
<el-radio :value="true">
启用
</el-radio>
<el-radio :value="false">
停用
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="内容" prop="notice_content">
@@ -206,13 +235,99 @@
<template #footer>
<div class="dialog-footer">
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
<el-button v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-else type="primary" @click="handleCloseDialog">确定</el-button>
<el-button @click="handleCloseDialog">取消</el-button>
<el-button v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">
确定
</el-button>
<el-button v-else type="primary" @click="handleCloseDialog">
确定
</el-button>
<el-button @click="handleCloseDialog">
取消
</el-button>
</div>
</template>
</el-dialog>
<!-- 导出弹窗 -->
<el-dialog v-model="exportsModalVisible" :align-center="true" title="导出数据" width="600px" style="padding-right: 0"
@close="handleCloseExportsModal">
<!-- 滚动 -->
<el-scrollbar max-height="60vh">
<!-- 表单 -->
<el-form ref="exportsFormRef" style="padding-right: var(--el-dialog-padding-primary)" :model="exportsFormData"
:rules="exportsFormRules">
<el-form-item label="文件名" prop="filename">
<el-input v-model="exportsFormData.filename" clearable />
</el-form-item>
<el-form-item label="工作表名" prop="sheetname">
<el-input v-model="exportsFormData.sheetname" clearable />
</el-form-item>
<el-form-item label="数据源" prop="origin">
<el-select v-model="exportsFormData.origin">
<el-option label="当前数据 (当前页的数据)" :value="ExportsOriginEnum.CURRENT" />
<el-option label="选中数据 (所有选中的数据)" :value="ExportsOriginEnum.SELECTED"
:disabled="selectionData.length <= 0" />
<el-option label="全量数据 (所有分页的数据)" :value="ExportsOriginEnum.REMOTE"
:disabled="contentConfig.exportsAction === undefined" />
</el-select>
</el-form-item>
<el-form-item label="字段" prop="fields">
<el-checkbox-group v-model="exportsFormData.fields">
<template v-for="col in cols" :key="col.prop">
<el-checkbox v-if="col.prop" :value="col.prop" :label="col.label" />
</template>
</el-checkbox-group>
</el-form-item>
</el-form>
</el-scrollbar>
<!-- 弹窗底部操作按钮 -->
<template #footer>
<div style="padding-right: var(--el-dialog-padding-primary)">
<el-button type="primary" @click="handleExportsSubmit"> </el-button>
<el-button @click="handleCloseExportsModal"> </el-button>
</div>
</template>
</el-dialog>
<!-- 导入弹窗 -->
<el-dialog v-model="importModalVisible" :align-center="true" title="导入数据" width="600px" style="padding-right: 0"
@close="handleCloseImportModal">
<!-- 滚动 -->
<el-scrollbar max-height="60vh">
<!-- 表单 -->
<el-form ref="importFormRef" style="padding-right: var(--el-dialog-padding-primary)" :model="importFormData"
:rules="importFormRules">
<el-form-item label="文件名" prop="files">
<el-upload ref="uploadRef" v-model:file-list="importFormData.files" class="w-full"
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
:drag="true" :limit="1" :auto-upload="false" :on-exceed="handleFileExceed">
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
<span>将文件拖到此处</span>
<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
*.xlsx / *.xls
<el-link v-if="contentConfig.importTemplate" type="primary" icon="download" underline="never"
@click="handleDownloadTemplate">
下载模板
</el-link>
</div>
</template>
</el-upload>
</el-form-item>
</el-form>
</el-scrollbar>
<!-- 弹窗底部操作按钮 -->
<template #footer>
<div style="padding-right: var(--el-dialog-padding-primary)">
<el-button type="primary" :disabled="importFormData.files.length === 0" @click="handleImportSubmit">
</el-button>
<el-button @click="handleCloseImportModal"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
@@ -231,14 +346,12 @@ const dataFormRef = ref();
const total = ref(0);
const selectIds = ref<number[]>([]);
const loading = ref(false);
const isExpand = ref(false);
const isExpandable = ref(true);
// 分页表单
const pageTableData = ref<NoticeTable[]>([]);
// 表格列配置
const tableColumns = ref([
{ prop: 'selection', label: '选择框', show: true },
@@ -250,6 +363,7 @@ const tableColumns = ref([
{ prop: 'description', label: '描述', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
{ prop: 'creator', label: '创建人', show: true },
{ prop: 'operation', label: '操作', show: true }
])
@@ -374,7 +488,7 @@ async function handleSubmit() {
const id = formData.id;
if (id) {
try {
await NoticeAPI.updateNotice(formData)
await NoticeAPI.updateNotice({ id, ...formData })
dialogVisible.visible = false;
resetForm();
handleCloseDialog();
@@ -401,9 +515,9 @@ async function handleSubmit() {
});
}
// 删除、导入、导出
async function handleOperation(type: 'delete' | 'import' | 'export', id?: number) {
if (type === 'delete' && !id && !selectIds.value.length) {
// 删除、批量删除
async function handleDelete(ids: number[]) {
if (!ids && !selectIds.value.length) {
ElMessageBox.confirm("确认删除该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
@@ -411,8 +525,7 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
}).then(async () => {
try {
loading.value = true;
await NoticeAPI.deleteNotice({ id: id ? id : selectIds.value });
ElMessage.success("删除成功");
await NoticeAPI.deleteNotice({ ids });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
@@ -423,89 +536,63 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
ElMessage.info('已取消删除');
});
}
else if (type === 'import') {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.xlsx, .xls';
input.click();
}
input.onchange = async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const formData = new FormData();
formData.append('file', file);
try {
loading.value = true;
await NoticeAPI.uploadFile(formData);
ElMessage.success('导入成功');
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
// 导出
async function handleExport() {
ElMessageBox.confirm('是否确认导出当前系统配置?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
const response = await NoticeAPI.exportNotice(body);
const blob = new Blob([JSON.stringify(response.data.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = '系统配置.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}
else if (type === 'export') {
ElMessageBox.confirm('是否确认导出当前系统配置?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
const response = await NoticeAPI.exportNotice(body);
const blob = new Blob([JSON.stringify(response.data.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = '系统配置.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
ElMessage.success('导出成功');
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessage.info('已取消导出');
});
}
else {
ElMessage.error('未知操作类型');
}
}).catch(() => {
ElMessage.info('已取消导出');
});
}
// 批量启用/停用
async function handleMoreClick(id: number) {
if (id && !selectIds.value.length) {
ElMessageBox.confirm("确认删除启用或停用该项数据?", "警告", {
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await NoticeAPI.batchAvailableNotice({ id: id ? id : selectIds.value });
await NoticeAPI.batchAvailableNotice({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
@@ -518,6 +605,129 @@ async function handleMoreClick(id: number) {
}
}
// // 导出
// function handleExport() {
// const filename = exportsFormData.filename
// ? exportsFormData.filename
// : props.contentConfig.permPrefix || "export";
// const sheetname = exportsFormData.sheetname ? exportsFormData.sheetname : "sheet";
// const workbook = new ExcelJS.Workbook();
// const worksheet = workbook.addWorksheet(sheetname);
// const columns: Partial<ExcelJS.Column>[] = [];
// cols.value.forEach((col) => {
// if (col.label && col.prop && exportsFormData.fields.includes(col.prop)) {
// columns.push({ header: col.label, key: col.prop });
// }
// });
// worksheet.columns = columns;
// if (exportsFormData.origin === ExportsOriginEnum.REMOTE) {
// if (props.contentConfig.exportsAction) {
// props.contentConfig.exportsAction(lastFormData).then((res) => {
// worksheet.addRows(res);
// workbook.xlsx
// .writeBuffer()
// .then((buffer) => {
// saveXlsx(buffer, filename as string);
// })
// .catch((error) => console.log(error));
// });
// } else {
// ElMessage.error("未配置exportsAction");
// }
// } else {
// worksheet.addRows(
// exportsFormData.origin === ExportsOriginEnum.SELECTED ? selectionData.value : pageData.value
// );
// workbook.xlsx
// .writeBuffer()
// .then((buffer) => {
// saveXlsx(buffer, filename as string);
// })
// .catch((error) => console.log(error));
// }
// }
// // 下载导入模板
// function handleDownloadTemplate() {
// const importTemplate = props.contentConfig.importTemplate;
// if (typeof importTemplate === "string") {
// window.open(importTemplate);
// } else if (typeof importTemplate === "function") {
// importTemplate().then((response) => {
// const fileData = response.data;
// const fileName = decodeURI(
// response.headers["content-disposition"].split(";")[1].split("=")[1]
// );
// saveXlsx(fileData, fileName);
// });
// } else {
// ElMessage.error("未配置importTemplate");
// }
// }
// // 导入
// function handleImport() {
// const importsAction = props.contentConfig.importsAction;
// if (importsAction === undefined) {
// ElMessage.error("未配置importsAction");
// return;
// }
// // 获取选择的文件
// const file = importFormData.files[0].raw as File;
// // 创建Workbook实例
// const workbook = new ExcelJS.Workbook();
// // 使用FileReader对象来读取文件内容
// const fileReader = new FileReader();
// // 二进制字符串的形式加载文件
// fileReader.readAsArrayBuffer(file);
// fileReader.onload = (ev) => {
// if (ev.target !== null && ev.target.result !== null) {
// const result = ev.target.result as ArrayBuffer;
// // 从 buffer中加载数据解析
// workbook.xlsx
// .load(result)
// .then((workbook) => {
// // 解析后的数据
// const data = [];
// // 获取第一个worksheet内容
// const worksheet = workbook.getWorksheet(1);
// if (worksheet) {
// // 获取第一行的标题
// const fields: any[] = [];
// worksheet.getRow(1).eachCell((cell) => {
// fields.push(cell.value);
// });
// // 遍历工作表的每一行(从第二行开始,因为第一行通常是标题行)
// for (let rowNumber = 2; rowNumber <= worksheet.rowCount; rowNumber++) {
// const rowData: IObject = {};
// const row = worksheet.getRow(rowNumber);
// // 遍历当前行的每个单元格
// row.eachCell((cell, colNumber) => {
// // 获取标题对应的键,并将当前单元格的值存储到相应的属性名中
// rowData[fields[colNumber - 1]] = cell.value;
// });
// // 将当前行的数据对象添加到数组中
// data.push(rowData);
// }
// }
// if (data.length === 0) {
// ElMessage.error("未解析到数据");
// return;
// }
// importsAction(data).then(() => {
// ElMessage.success("导入数据成功");
// handleCloseImportModal();
// handleRefresh(true);
// });
// })
// .catch((error) => console.log(error));
// } else {
// ElMessage.error("读取文件失败");
// }
// };
// }
onMounted(() => {
loadingData();
});
+92 -119
View File
@@ -8,14 +8,15 @@
<el-input v-model="queryFormData.name" placeholder="请输入岗位名称" clearable />
</el-form-item>
<el-form-item prop="status" label="状态">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable style="width: 160px">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
</el-form-item>
<!-- 时间范围收起状态下隐藏 -->
<el-form-item v-if="isExpand" prop="start_time" label="创建时间">
<el-date-picker v-model="queryFormData.start_time" type="daterange" value-format="yyyy-MM-dd" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" />
<el-date-picker v-model="queryFormData.start_time" type="daterange" value-format="yyyy-MM-dd"
range-separator="" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
<!-- 查询重置展开/收起按钮 -->
<el-form-item class="search-buttons">
@@ -56,38 +57,23 @@
<div class="data-table__toolbar">
<div class="data-table__toolbar--actions">
<el-button type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleOperation('delete')">批量删除</el-button>
<el-dropdown>
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0"
@click="handleDelete(selectIds)">批量删除</el-button>
<el-dropdown trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-menu @click="handleMoreClick">
<el-menu-item key="1">
<span>
<el-icon>
<Check />
</el-icon>
<span>批量启用</span>
</span>
</el-menu-item>
<el-menu-item key="2">
<span>
<el-icon>
<CircleClose />
</el-icon>
<span>批量停用</span>
</span>
</el-menu-item>
</el-menu>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="data-table__toolbar--tools">
<el-tooltip content="导入">
<el-button type="info" icon="upload" circle @click="handleOperation('import')" />
</el-tooltip>
<el-tooltip content="导出">
<el-button type="warning" icon="download" circle @click="handleOperation('export')" />
<el-button type="warning" icon="download" circle @click="handleExport" />
</el-tooltip>
<el-tooltip content="刷新">
<el-button type="primary" icon="refresh" circle @click="handleRefresh" />
@@ -104,43 +90,55 @@
</template>
</el-dropdown>
</el-tooltip>
</div>
</div>
<!-- 表格区域系统配置列表 -->
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row class="data-table__content" height="450" border stripe @selection-change="handleSelectionChange">
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row
class="data-table__content" height="450" border stripe @selection-change="handleSelectionChange">
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" width="55" align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号" width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'name')?.show" key="name" label="岗位名称" prop="name" min-width="80" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" key="status" label="状态" prop="status" min-width="60">
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" width="55"
align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号"
width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'name')?.show" key="name" label="岗位名称" prop="name"
min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" key="status" label="状态"
prop="status" min-width="80">
<template #default="scope">
<el-tag :type="scope.row.status === true ? 'success' : 'danger'">
{{ scope.row.status === true ? "启用" : "停用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'order')?.show" key="order" label="岗位排序" prop="order" min-width="100" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description" label="描述" prop="description" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间" prop="created_at" min-width="120" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at" label="更新时间" prop="updated_at" min-width="120" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'order')?.show" key="order" label="岗位排序"
prop="order" min-width="80" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description" label="描述"
prop="description" min-width="120" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间"
prop="created_at" min-width="120" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at" label="更新时间"
prop="updated_at" min-width="120" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作" min-width="120">
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作"
min-width="150">
<template #default="scope">
<el-button type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button type="danger" size="small" link icon="delete" @click="handleOperation('delete', scope.row.id)">删除</el-button>
<el-button type="info" size="small" link icon="document"
@click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button type="primary" size="small" link icon="edit"
@click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button type="danger" size="small" link icon="delete"
@click="handleDelete([scope.row.id])">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size"
@pagination="loadingData" />
</template>
</el-card>
@@ -179,7 +177,8 @@
</el-radio-group>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit type="textarea" placeholder="请输入描述" />
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit type="textarea"
placeholder="请输入描述" />
</el-form-item>
</el-form>
</template>
@@ -230,6 +229,7 @@ const tableColumns = ref([
{ prop: 'description', label: '描述', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
{ prop: 'creator', label: '创建人', show: true },
{ prop: 'operation', label: '操作', show: true }
])
@@ -376,9 +376,9 @@ async function handleSubmit() {
});
}
// 删除、导入、导出
async function handleOperation(type: 'delete' | 'import' | 'export', id?: number) {
if (type === 'delete' && !id && !selectIds.value.length) {
// 删除、批量删除
async function handleDelete(ids: number[]) {
if (!ids && !selectIds.value.length) {
ElMessageBox.confirm("确认删除该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
@@ -386,8 +386,7 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
}).then(async () => {
try {
loading.value = true;
await PositionAPI.deletePosition({ id: id ? id : selectIds.value });
ElMessage.success("删除成功");
await PositionAPI.deletePosition({ ids });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
@@ -398,89 +397,63 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
ElMessage.info('已取消删除');
});
}
else if (type === 'import') {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.xlsx, .xls';
input.click();
}
input.onchange = async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const formData = new FormData();
formData.append('file', file);
try {
loading.value = true;
await PositionAPI.uploadFile(formData);
ElMessage.success('导入成功');
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
// 导出
async function handleExport() {
ElMessageBox.confirm('是否确认导出当前系统配置?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
const response = await PositionAPI.exportPosition(body);
const blob = new Blob([JSON.stringify(response.data.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = '系统配置.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}
else if (type === 'export') {
ElMessageBox.confirm('是否确认导出岗位?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
const response = await PositionAPI.exportPosition(body);
const blob = new Blob([JSON.stringify(response.data.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = '系统配置.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
ElMessage.success('导出成功');
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessage.info('已取消导出');
});
}
else {
ElMessage.error('未知操作类型');
}
}).catch(() => {
ElMessage.info('已取消导出');
});
}
// 批量启用/停用
async function handleMoreClick(id: number) {
if (id && !selectIds.value.length) {
ElMessageBox.confirm("确认删除启用或停用该项数据?", "警告", {
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await PositionAPI.batchAvailablePosition({ id: id ? id : selectIds.value });
await PositionAPI.batchAvailablePosition({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
+168 -181
View File
@@ -8,7 +8,7 @@
<el-input v-model="queryFormData.name" placeholder="请输入岗位名称" clearable />
</el-form-item>
<el-form-item prop="status" label="状态">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable style="width: 160px">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
@@ -58,38 +58,22 @@
<div class="data-table__toolbar--actions">
<el-button type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0"
@click="handleOperation('delete')">批量删除</el-button>
<el-dropdown>
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多
@click="handleDelete(selectIds)">批量删除</el-button>
<el-dropdown trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-menu @click="handleMoreClick">
<el-menu-item key="1">
<span>
<el-icon>
<Check />
</el-icon>
<span>批量启用</span>
</span>
</el-menu-item>
<el-menu-item key="2">
<span>
<el-icon>
<CircleClose />
</el-icon>
<span>批量停用</span>
</span>
</el-menu-item>
</el-menu>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="data-table__toolbar--tools">
<el-tooltip content="导入">
<el-button type="info" icon="upload" circle @click="handleOperation('import')"/>
</el-tooltip>
<el-tooltip content="导出">
<el-button type="warning" icon="download" circle @click="handleOperation('export')"/>
<el-button type="warning" icon="download" circle @click="handleExport" />
</el-tooltip>
<el-tooltip content="刷新">
<el-button type="primary" icon="refresh" circle @click="handleRefresh" />
@@ -119,11 +103,11 @@
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号"
width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'name')?.show" key="name" label="角色名称" prop="name"
min-width="80" />
min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'data_scope')?.show" key="data_scope" label="数据权限"
prop="data_scope" min-width="80" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'order')?.show" key="order" label="岗位排序"
prop="order" min-width="100" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'order')?.show" key="order" label="排序"
prop="order" min-width="80" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" key="status" label="状态"
prop="status" min-width="60">
<template #default="scope">
@@ -135,12 +119,12 @@
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description" label="描述"
prop="description" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间"
prop="created_at" min-width="120" sortable />
prop="created_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at" label="更新时间"
prop="updated_at" min-width="120" sortable />
prop="updated_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作"
min-width="220">
min-width="260">
<template #default="scope">
<el-button type="primary" size="small" link icon="position"
@click="handleOpenAssignPermDialog(scope.row)">分配权限</el-button>
@@ -149,21 +133,43 @@
<el-button type="primary" size="small" link icon="edit"
@click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button type="danger" size="small" link icon="delete"
@click="handleOperation('delete', scope.row.id)">删除</el-button>
@click="handleDelete([scope.row.id])">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-model:total="total" v-model:page="queryFormData.page_no"
v-model:limit="queryFormData.page_size" @pagination="loadingData" />
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size"
@pagination="loadingData" />
</template>
</el-card>
<!-- 角色表单弹窗 -->
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" width="500px" @close="handleCloseDialog">
<el-form ref="roleFormRef" :model="formData" :rules="rules" label-width="100px">
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="2" border>
<el-descriptions-item label="角色名称" :span="2">{{ detailFormData.name }}</el-descriptions-item>
<el-descriptions-item label="排序" :span="2">
{{ detailFormData.order }}
</el-descriptions-item>
<el-descriptions-item label="数据权限" :span="2">
{{ detailFormData.data_scope }}
</el-descriptions-item>
<el-descriptions-item label="状态" :span="2">
<el-tag v-if="detailFormData.status" type="success">启用</el-tag>
<el-tag v-else type="danger">停用</el-tag>
</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">{{ detailFormData.description }}</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">{{ detailFormData.creator?.name }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ detailFormData.created_at }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ detailFormData.updated_at }}</el-descriptions-item>
</el-descriptions>
</template>
<!-- 新增编辑表单 -->
<template v-else>
<el-form ref="roleFormRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="角色名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入角色名称" />
</el-form-item>
@@ -185,6 +191,9 @@
</el-form-item>
</el-form>
</template>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSubmit"> </el-button>
@@ -195,78 +204,85 @@
<!-- 分配权限弹窗 -->
<el-drawer v-model="assignPermDialogVisible" :title="'【' + checkedRole.name + '】权限分配'" :size="drawerSize">
<div style="min-width: 300px;">
<div style="display: flex; gap: 10px; ">
<div style="width: 10px; background-color: #1677ff;"></div>
<div>
<span style="font-size: 16px;">数据授权</span>
<a-tooltip placement="right">
<template #title>
<span>授权用户可操作的数据范围</span>
</template>
<QuestionCircleOutlined style="margin-left: 5px;" />
</a-tooltip>
<!-- 使用 element-plus row col 组件实现左右两部分并排显示 -->
<el-row :gutter="20" style="height: calc(100% - 60px);">
<!-- 左侧数据权限设置占据 8 -->
<el-col :span="8">
<div style="display: flex; gap: 10px; ">
<div style="width: 10px; background-color: #1677ff;"></div>
<div>
<span style="font-size: 16px;">数据授权</span>
<a-tooltip placement="right">
<template #title>
<span>授权用户可操作的数据范围</span>
</template>
<QuestionCircleOutlined style="margin-left: 5px;" />
</a-tooltip>
</div>
</div>
</div>
<el-form ref="roleFormRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="数据权限" prop="data_scope">
<el-select v-model="permissionState.data_scope">
<el-option :key="1" label="全部数据" :value="1" />
<el-option :key="2" label="部门及子部门数据" :value="2" />
<el-option :key="3" label="本部门数据" :value="3" />
<el-option :key="4" label="本人数据" :value="4" />
<el-option :key="5" label="自定义数据" :value="5" />
</el-select>
</el-form-item>
</el-form>
<el-form ref="roleFormRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="数据权限" prop="data_scope">
<el-select v-model="permissionState.data_scope">
<el-option :key="1" label="全部数据" :value="1" />
<el-option :key="2" label="部门及子部门数据" :value="2" />
<el-option :key="3" label="本部门数据" :value="3" />
<el-option :key="4" label="本人数据" :value="4" />
<el-option :key="5" label="自定义数据" :value="5" />
</el-select>
</el-form-item>
</el-form>
<el-tree v-if="permissionState.data_scope === 5 && deptTreeData.length" :checked-keys="permissionState.dept_ids"
:row-key="record => record.id" :tree-data="deptTreeData" :default-expand-all=true
:field-names="{ children: 'children', title: 'name', key: 'id' }" checkable check-strictly
style="margin-top: 15px;" @check="deptTreeCheck" />
</div>
<el-tree v-if="permissionState.data_scope === 5 && deptTreeData.length"
:checked-keys="permissionState.dept_ids" :row-key="record => record.id" :tree-data="deptTreeData"
:default-expand-all=true :field-names="{ children: 'children', title: 'name', key: 'id' }" checkable
check-strictly style="margin-top: 15px; height: calc(100% - 100px); overflow: auto;"
@check="deptTreeCheck" />
</el-col>
<!-- 右侧菜单功能权限设置,占据 16 列 -->
<el-col :span="16">
<div class="flex-x-between">
<div style="width: 10px; background-color: #1677ff;"></div>
<span style="font-size: 16px;">菜单授权</span>
<el-input v-model="permKeywords" clearable class="w-[150px]" placeholder="菜单权限名称">
<template #prefix>
<Search />
</template>
</el-input>
<div class="flex-center ml-5">
<el-button type="primary" size="small" plain @click="togglePermTree">
<template #icon>
<Switch />
</template>
{{ isExpanded ? "收缩" : "展开" }}
</el-button>
<el-checkbox v-model="parentChildLinked" class="ml-5" @change="handleparentChildLinkedChange">
父子联动
</el-checkbox>
<div class="flex-x-between">
<div style="width: 10px; background-color: #1677ff;"></div>
<span style="font-size: 16px;">菜单授权</span>
<el-input v-model="permKeywords" clearable class="w-[150px]" placeholder="菜单权限名称">
<template #prefix>
<Search />
</template>
</el-input>
<el-tooltip placement="bottom">
<template #content>
如果只需勾选菜单权限,不需要勾选子菜单或者按钮权限,请关闭父子联动
</template>
<el-icon class="ml-1 color-[--el-color-primary] inline-block cursor-pointer">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</div>
<div class="flex-center ml-5">
<el-button type="primary" size="small" plain @click="togglePermTree">
<template #icon>
<Switch />
<el-tree ref="permTreeRef" node-key="value" show-checkbox :data="menuTreeData"
:filter-node-method="handlePermFilter" :default-expand-all="true" :check-strictly="!parentChildLinked"
class="mt-5" style="height: calc(100% - 60px); overflow: auto;">
<template #default="{ data }">
{{ data.label }}
</template>
{{ isExpanded ? "收缩" : "展开" }}
</el-button>
<el-checkbox v-model="parentChildLinked" class="ml-5" @change="handleparentChildLinkedChange">
父子联动
</el-checkbox>
</el-tree>
</el-col>
</el-row>
<el-tooltip placement="bottom">
<template #content>
如果只需勾选菜单权限,不需要勾选子菜单或者按钮权限,请关闭父子联动
</template>
<el-icon class="ml-1 color-[--el-color-primary] inline-block cursor-pointer">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</div>
<el-tree ref="permTreeRef" node-key="value" show-checkbox :data="menuPermOptions"
:filter-node-method="handlePermFilter" :default-expand-all="true" :check-strictly="!parentChildLinked"
class="mt-5">
<template #default="{ data }">
{{ data.label }}
</template>
</el-tree>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleAssignPermSubmit">确 定</el-button>
@@ -316,8 +332,6 @@ const menuTreeData = ref<permissionMenuType[]>([]);
// 分页表单
const pageTableData = ref<RoleTable[]>([]);
// 菜单权限下拉
const menuPermOptions = ref<OptionType[]>([]);
// 表格列配置
const tableColumns = ref([
@@ -443,14 +457,14 @@ async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: numbe
if (id) {
const response = await RoleAPI.getRoleDetail({ id });
if (type === 'detail') {
dialogVisible.title = "岗位详情";
dialogVisible.title = "角色详情";
Object.assign(detailFormData.value, response.data.data);
} else if (type === 'update') {
dialogVisible.title = "修改岗位";
dialogVisible.title = "修改角色";
Object.assign(formData, response.data.data);
}
} else {
dialogVisible.title = "新增岗位";
dialogVisible.title = "新增角色";
formData.id = undefined;
}
dialogVisible.visible = true;
@@ -491,9 +505,9 @@ async function handleSubmit() {
});
}
// 删除、导入、导出
async function handleOperation(type: 'delete' | 'import' | 'export', id?: number) {
if (type === 'delete' && !id && !selectIds.value.length) {
// 删除、批量删除
async function handleDelete(ids: number[]) {
if (!ids && !selectIds.value.length) {
ElMessageBox.confirm("确认删除该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
@@ -501,8 +515,7 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
}).then(async () => {
try {
loading.value = true;
await RoleAPI.deleteRole({ id: id ? id : selectIds.value });
ElMessage.success("删除成功");
await RoleAPI.deleteRole({ ids });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
@@ -513,89 +526,63 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
ElMessage.info('已取消删除');
});
}
else if (type === 'import') {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.xlsx, .xls';
input.click();
}
input.onchange = async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const formData = new FormData();
formData.append('file', file);
try {
loading.value = true;
await RoleAPI.uploadFile(formData);
ElMessage.success('导入成功');
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
// 导出
async function handleExport() {
ElMessageBox.confirm('是否确认导出当前系统配置?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
const response = await RoleAPI.exportRole(body);
const blob = new Blob([JSON.stringify(response.data.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = '系统配置.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}
else if (type === 'export') {
ElMessageBox.confirm('是否确认导出岗位?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value
};
ElMessage.warning('正在导出数据,请稍候...');
const response = await RoleAPI.exportRole(body);
const blob = new Blob([JSON.stringify(response.data.data)], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = '系统配置.xlsx';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);
if (fileNameMatch) {
fileName = decodeURIComponent(fileNameMatch[1]);
}
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
ElMessage.success('导出成功');
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessage.info('已取消导出');
});
}
else {
ElMessage.error('未知操作类型');
}
}).catch(() => {
ElMessage.info('已取消导出');
});
}
// 批量启用/停用
async function handleMoreClick(id: number) {
if (id && !selectIds.value.length) {
ElMessageBox.confirm("确认删除启用或停用该项数据?", "警告", {
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await RoleAPI.batchAvailableRole({ id: id ? id : selectIds.value });
await RoleAPI.batchAvailableRole({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
@@ -619,7 +606,7 @@ async function handleOpenAssignPermDialog(row: RoleTable) {
checkedRole.value.name = row.name;
// 获取所有的菜单
menuPermOptions.value = await MenuAPI.getOptions();
menuTreeData.value = await MenuAPI.getMenuList();
// 回显角色已拥有的菜单
RoleAPI.getRoleMenuIds(roleId)
@@ -642,7 +629,7 @@ function handleAssignPermSubmit() {
.map((node: any) => node.value);
loading.value = true;
RoleAPI.updateRoleMenus(roleId, checkedMenuIds)
RoleAPI.setPermission(roleId, checkedMenuIds)
.then(() => {
ElMessage.success("分配权限成功");
assignPermDialogVisible.value = false;
+57 -77
View File
@@ -4,7 +4,7 @@
<el-row :gutter="20">
<!-- 部门树 -->
<el-col :lg="4" :xs="24" class="mb-[12px]">
<DeptTree v-model="UserPageQuery.dept_id" @node-click="handleQuery" />
<DeptTree v-model="queryFormData.dept_id" @node-click="handleQuery" />
</el-col>
<!-- 用户列表 -->
@@ -19,7 +19,7 @@
<el-input v-model="queryFormData.name" placeholder="请输入姓名" clearable />
</el-form-item>
<el-form-item prop="status" label="状态">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable style="width: 160px">
<el-select v-model="queryFormData.status" placeholder="请选择状态" clearable>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
@@ -68,29 +68,16 @@
<div class="data-table__toolbar--actions">
<el-button type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0"
@click="handleOperation('delete')">批量删除</el-button>
<el-dropdown>
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多
@click="handleDelete(selectIds)">批量删除</el-button>
<el-dropdown trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-menu @click="handleMoreClick">
<el-menu-item key="1">
<span>
<el-icon>
<Check />
</el-icon>
<span>批量启用</span>
</span>
</el-menu-item>
<el-menu-item key="2">
<span>
<el-icon>
<CircleClose />
</el-icon>
<span>批量停用</span>
</span>
</el-menu-item>
</el-menu>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
@@ -135,46 +122,42 @@
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" width="55"
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" min-width="55"
align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号"
width="60" />
<el-table-column label="头像" prop="avatar">
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号" align="center" min-width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'avatar')?.show" label="头像" prop="avatar" min-width="80" align="center">
<template #default="scope">
<el-avatar :src="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column label="账号" prop="username" />
<el-table-column label="用户名" align="center" prop="name" />
<el-table-column label="状态" align="center" prop="status" width="80">
<el-table-column v-if="tableColumns.find(col => col.prop === 'username')?.show" label="账号" prop="username" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'name')?.show" label="用户名" prop="name" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" label="状态" prop="status" min-width="100">
<template #default="scope">
<el-tag :type="scope.row.status === true ? 'success' : 'danger'">
{{ scope.row.status === true ? "启用" : "停用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="是否超管" align="center" prop="is_superuser">
<el-table-column v-if="tableColumns.find(col => col.prop === 'is_superuser')?.show" label="是否超管" prop="is_superuser" min-width="100">
<template #default="scope">
<el-tag :type="scope.row.is_superuser ? 'success' : 'info'">
{{ scope.row.is_superuser ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="性别" align="center">
<el-table-column v-if="tableColumns.find(col => col.prop === 'gender')?.show" label="性别" min-width="100">
<template #default="scope">
<DictLabel v-model="scope.row.gender" code="gender" />
</template>
</el-table-column>
<el-table-column label="部门" align="center" prop="dept" />
<el-table-column label="角色" align="center" prop="roles" />
<el-table-column label="岗位" align="center" prop="positions" />
<el-table-column label="手机号" align="center" prop="mobile" width="120" />
<el-table-column label="邮箱" align="center" prop="email" width="160" />
<el-table-column label="创建时间" align="center" prop="createTime" width="150" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'mobile')?.show" label="手机号" prop="mobile" min-width="160" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'email')?.show" label="邮箱" prop="email" min-width="160" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" label="创建时间" prop="created_at" min-width="200" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" label="更新时间" prop="updated_at" min-width="200" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作"
min-width="120">
min-width="280">
<template #default="scope">
<el-button type="primary" icon="RefreshLeft" size="small" link @click="hancleResetPassword(scope.row)">
重置密码
@@ -184,7 +167,7 @@
<el-button type="primary" size="small" link icon="edit"
@click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button type="danger" size="small" link icon="delete"
@click="handleOperation('delete', scope.row.id)">删除</el-button>
@click="handleDelete([scope.row.id])">删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -296,7 +279,7 @@
<template #footer>
<div class="dialog-footer">
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
<el-button v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-if="dialogVisible.type === 'create' || dialogVisible.type === 'update'" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-else type="primary" @click="handleCloseDialog">确定</el-button>
<el-button @click="handleCloseDialog">取消</el-button>
</div>
@@ -304,7 +287,6 @@
</el-form>
</template>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSubmit"> </el-button>
@@ -366,17 +348,12 @@ const tableColumns = ref([
{ prop: 'index', label: '序号', show: true },
{ prop: 'avatar', label: '头像', show: true },
{ prop: 'username', label: '账号', show: true },
{ prop: 'name', label: '昵称', show: true },
{ prop: 'dept_name', label: '部门', show: true },
{ prop: 'roleNames', label: '角色', show: true },
{ prop: 'positionNames', label: '岗位', show: true },
{ prop: 'password', label: '密码', show: true },
{ prop: 'name', label: '用户名', show: true },
{ prop: 'gender', label: '性别', show: true },
{ prop: 'email', label: '邮箱', show: true },
{ prop: 'mobile', label: '手机号', show: true },
{ prop: 'is_superuser', label: '是否超管', show: true },
{ prop: 'status', label: '状态', show: true },
{ prop: 'status', label: '状态', show: true },
{ prop: 'description', label: '描述', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
@@ -506,7 +483,7 @@ function hancleResetPassword(row: UserInfo) {
ElMessage.warning("密码至少需要6位字符,请重新输入");
return false;
}
UserAPI.resetPassword(row.id, value).then(() => {
UserAPI.changeCurrentUserPassword(row.id, value).then(() => {
ElMessage.success("密码重置成功,新密码是:" + value);
});
},
@@ -579,28 +556,8 @@ const handleSubmit = useDebounceFn(async () => {
}, 1000)
// 删除、导入、导出
async function handleOperation(type: 'delete' | 'import' | 'export', id?: number) {
if (type === 'delete' && !id && !selectIds.value.length) {
ElMessageBox.confirm("确认删除该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await UserAPI.deleteUser({ id: id ? id : selectIds.value });
ElMessage.success("删除成功");
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessage.info('已取消删除');
});
}
else if (type === 'import') {
async function handleOperation(type: 'import' | 'export') {
if (type === 'import') {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.xlsx, .xls';
@@ -692,17 +649,40 @@ async function handleOperation(type: 'delete' | 'import' | 'export', id?: number
}
}
// 批量启用/停用
async function handleMoreClick(id: number) {
if (id && !selectIds.value.length) {
ElMessageBox.confirm("确认删除启用或停用该项数据?", "警告", {
// 删除、批量删除
async function handleDelete(ids: number[]) {
if (!ids && !selectIds.value.length) {
ElMessageBox.confirm("确认删除该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await UserAPI.batchAvailableUser({ id: id ? id : selectIds.value });
await UserAPI.deleteUser({ids});
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessage.info('已取消删除');
});
}
}
// 批量启用/停用
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm("确认启用或停用该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await UserAPI.batchAvailableUser({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);