feat(settings): 添加项目引导可见性配置项

feat(api): 修改导出接口返回类型为Blob并统一处理导出逻辑

feat(router): 添加404路由匹配规则

fix(permission): 优化动态路由处理逻辑

feat(login): 登录成功后自动显示项目引导

feat(navbar): 优化用户头像显示和引导可见性控制

feat(store): 添加引导可见性状态管理

refactor(views): 统一导出功能实现方式

fix(backend): 添加导出数据中的创建者字段

refactor(user): 优化用户导入导出功能

refactor(job): 重构定时任务页面和导出逻辑
This commit is contained in:
zhangtao
2025-07-30 00:08:30 +08:00
parent 9050de72d5
commit 2048820832
34 changed files with 411 additions and 398 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ const JobAPI = {
},
exportJob(body: JobPageQuery) {
return request<ApiResponse>({
return request<Blob>({
url: `/monitor/job/export`,
method: "post",
data: body,
+1 -1
View File
@@ -57,7 +57,7 @@ const ConfigAPI = {
},
exportConfig(body: ConfigPageQuery) {
return request<ApiResponse>({
return request<Blob>({
url: `/system/config/export`,
method: "post",
data: body,
+2 -2
View File
@@ -56,7 +56,7 @@ const DictAPI = {
},
exportDictType(body: DictPageQuery) {
return request<ApiResponse>({
return request<Blob>({
url: `/system/dict/type/export`,
method: "post",
data: body,
@@ -112,7 +112,7 @@ const DictAPI = {
},
exportDictData(body: DictDataPageQuery) {
return request<ApiResponse>({
return request<Blob>({
url: `/system/dict/data/export`,
method: "post",
data: body,
+3 -3
View File
@@ -24,11 +24,11 @@ const LogAPI = {
});
},
exportLog(query: LogPageQuery) {
return request<ApiResponse>({
exportLog(body: LogPageQuery) {
return request<Blob>({
url: `/system/log/export`,
method: "post",
data: query,
data: body,
responseType: "blob",
});
},
+1 -1
View File
@@ -56,7 +56,7 @@ const NoticeAPI = {
},
exportNotice(body: NoticePageQuery) {
return request<ApiResponse>({
return request<Blob>({
url: `/system/notice/export`,
method: "post",
data: body,
+1 -1
View File
@@ -49,7 +49,7 @@ const PositionAPI = {
},
exportPosition(body: PositionPageQuery) {
return request<ApiResponse>({
return request<Blob>({
url: `/system/position/export`,
method: "post",
data: body,
+5 -5
View File
@@ -56,11 +56,11 @@ const RoleAPI = {
});
},
exportRole(query: TablePageQuery) {
return request<ApiResponse>({
exportRole(body: TablePageQuery) {
return request<Blob>({
url: `/system/role/export`,
method: "post",
data: query,
data: body,
responseType: "blob",
});
},
@@ -79,8 +79,8 @@ export interface TablePageQuery extends PageQuery {
export interface RoleTable {
index?: number;
id?: number;
name?: string;
id: number;
name: string;
order?: number;
data_scope?: number;
status?: boolean;
+3 -3
View File
@@ -105,11 +105,11 @@ export const UserAPI = {
});
},
exportUser(query: UserPageQuery) {
return request<ApiResponse>({
exportUser(body: UserPageQuery) {
return request<Blob>({
url: `/system/user/export`,
method: "post",
params: query,
data: body,
responseType: "blob",
});
},
@@ -32,7 +32,7 @@
<el-dropdown trigger="click">
<div class="user-profile">
<template v-if="userStore.basicInfo.avatar">
<el-avatar :src="userStore.basicInfo.avatar" />
<el-avatar size="small" :src="userStore.basicInfo.avatar" />
</template>
<template v-else>
<el-avatar icon="UserFilled" />
@@ -158,7 +158,13 @@ function handleGiteeClick() {
/**
* 项目引导
*/
const guideVisible = ref<boolean>(false)
// 使refcomputed便
// 使 computed watch 使
const guideVisible = computed({
get: () => appStore.guideVisible,
set: (newValue) => appStore.showGuide(newValue)
});
function handleTourClick() {
//
if (appStore.device === DeviceEnum.MOBILE) {
@@ -283,13 +289,9 @@ function logout() {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
padding: 0 8px;
&__avatar {
flex-shrink: 0;
width: 28px;
height: 28px;
border-radius: 50%;
}
+5
View File
@@ -40,6 +40,11 @@ export const constantRoutes: RouteRecordRaw[] = [
meta: { hidden: true, title: "500" },
component: () => import("@/views/error/500.vue"),
},
{
path: "/:pathMatch(.*)*",
component: () => import('@/views/error/404.vue'),
meta: { hidden: true, title: "404" },
},
// 以下内容必须放在后面
{
path: "/",
+2
View File
@@ -33,6 +33,8 @@ export const defaultSettings: AppSettings = {
watermarkContent: pkg.name,
// 侧边栏配色方案
sidebarColorScheme: SidebarColor.CLASSIC_BLUE,
// 项目引导
guideVisible: false,
};
// 主题色预设 - 经典配色方案
+25 -6
View File
@@ -23,6 +23,8 @@ export const useAppStore = defineStore("app", () => {
// 顶部菜单激活路径
const activeTopMenuPath = useStorage("activeTopMenuPath", "");
// 项目引导
const guideVisible = useStorage("guideVisible", defaultSettings.guideVisible);
/**
*
@@ -35,32 +37,40 @@ export const useAppStore = defineStore("app", () => {
}
});
// 切换侧边栏
/**
*
*/
function toggleSidebar() {
sidebar.opened = !sidebar.opened;
sidebarStatus.value = sidebar.opened ? SidebarStatus.OPENED : SidebarStatus.CLOSED;
}
// 关闭侧边栏
/**
*
*/
function closeSideBar() {
sidebar.opened = false;
sidebarStatus.value = SidebarStatus.CLOSED;
}
// 打开侧边栏
/**
*
*/
function openSideBar() {
sidebar.opened = true;
sidebarStatus.value = SidebarStatus.OPENED;
}
// 切换设备
/**
*
* @param val
*/
function toggleDevice(val: string) {
device.value = val;
}
/**
*
*
* @param val default | small | large
*/
function changeSize(val: string) {
@@ -68,7 +78,6 @@ export const useAppStore = defineStore("app", () => {
}
/**
*
*
* @param val
*/
function changeLanguage(val: string) {
@@ -76,10 +85,18 @@ export const useAppStore = defineStore("app", () => {
}
/**
*
* @param val
*/
function activeTopMenu(val: string) {
activeTopMenuPath.value = val;
}
/**
*
* @param val
*/
function showGuide(val: boolean) {
guideVisible.value = val;
}
return {
device,
sidebar,
@@ -88,12 +105,14 @@ export const useAppStore = defineStore("app", () => {
size,
activeTopMenu,
toggleDevice,
showGuide,
changeSize,
changeLanguage,
toggleSidebar,
closeSideBar,
openSideBar,
activeTopMenuPath,
guideVisible,
};
});
@@ -112,16 +112,16 @@ export const usePermissionStore = defineStore("permission", () => {
const routersTree = listToTree(userStore.routeList);
const routerMap = generator(routersTree);
// 从用户绑定的路由列表获取数据
// 解析动态路由
const dynamicRoutes = parseDynamicRoutes(routerMap);
// 重置路由,移除旧的动态路由
resetRouter();
// resetRouter();
// 重新添加所有路由
constantRoutes.forEach(route => {
router.addRoute(route);
});
// constantRoutes.forEach(route => {
// router.addRoute(route);
// });
routes.value = [...constantRoutes, ...dynamicRoutes];
+2
View File
@@ -84,6 +84,8 @@ declare global {
watermarkContent: string;
/** 侧边栏配色方案 */
sidebarColorScheme: "classic-blue" | "minimal-white";
/** 项目引导 */
guideVisible: boolean;
}
/**
+135 -72
View File
@@ -3,7 +3,12 @@
<div class="app-container">
<!-- 搜索区域 -->
<div class="search-container">
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":" >
<el-form
ref="queryFormRef"
:model="queryFormData"
:inline="true"
label-suffix=":"
>
<el-form-item prop="name" label="任务名称">
<el-input
v-model="queryFormData.name"
@@ -204,11 +209,21 @@
删除
</el-button>
<el-dropdown trigger="click">
<el-button type="warning" size="small" link icon="ArrowDown">更多</el-button>
<el-button type="warning" size="small" link icon="ArrowDown"
>更多</el-button
>
<template #dropdown>
<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="Check"
@click="handleOption(scope.row, 1)"
>暂停</el-dropdown-item
>
<el-dropdown-item
icon="CircleClose"
@click="handleOption(scope.row, 2)"
>恢复</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -302,15 +317,30 @@
</template>
<!-- 新增编辑表单 -->
<template v-else>
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" inline>
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-suffix=":"
label-width="auto"
inline
>
<el-form-item label="任务名称" prop="name" style="width: 40%">
<el-input v-model="formData.name" placeholder="请输入任务名称" :maxlength="50"/>
<el-input
v-model="formData.name"
placeholder="请输入任务名称"
:maxlength="50"
/>
</el-form-item>
<el-form-item label="任务函数" prop="func" style="width: 40%">
<el-input v-model="formData.func" placeholder="请输入任务函数" :maxlength="50"/>
<el-input
v-model="formData.func"
placeholder="请输入任务函数"
:maxlength="50"
/>
</el-form-item>
<el-form-item label="存储器" prop="jobstore" style="width: 40%">
<el-select v-model="formData.jobstore" placeholder="请选择存储器" >
<el-select v-model="formData.jobstore" placeholder="请选择存储器">
<el-option value="default" label="默认(Memory)" />
<el-option value="sqlalchemy" label="数据库" />
<el-option value="redis" label="Redis存储器" />
@@ -323,10 +353,18 @@
</el-select>
</el-form-item>
<el-form-item label="位置参数" prop="args" style="width: 40%">
<el-input v-model="formData.args" placeholder="请输入位置参数" :maxlength="50"/>
<el-input
v-model="formData.args"
placeholder="请输入位置参数"
:maxlength="50"
/>
</el-form-item>
<el-form-item label="关键字参数" prop="kwargs" style="width: 40%">
<el-input v-model="formData.kwargs" placeholder="请输入关键字参数" :maxlength="50"/>
<el-input
v-model="formData.kwargs"
placeholder="请输入关键字参数"
:maxlength="50"
/>
</el-form-item>
<el-form-item label="并发执行" prop="coalesce" style="width: 40%">
<el-radio-group v-model="formData.coalesce">
@@ -334,8 +372,17 @@
<el-radio :value="false"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="最大实例数" prop="max_instances" style="width: 40%">
<el-input-number v-model="formData.max_instances" controls-position="right" :min="1" :max="10"/>
<el-form-item
label="最大实例数"
prop="max_instances"
style="width: 40%"
>
<el-input-number
v-model="formData.max_instances"
controls-position="right"
:min="1"
:max="10"
/>
</el-form-item>
<el-form-item label="触发器" prop="trigger" style="width: 40%">
<el-select v-model="formData.trigger" placeholder="请选择触发器">
@@ -364,7 +411,9 @@
v-else-if="formData.trigger === 'interval'"
label="间隔时间"
prop="trigger_args"
:rules="[{ required: true, message: '请输入间隔时间', trigger: 'change' }]"
:rules="[
{ required: true, message: '请输入间隔时间', trigger: 'change' },
]"
style="width: 40%"
>
<el-popover
@@ -393,7 +442,13 @@
v-else-if="formData.trigger === 'cron'"
label="Cron表达式"
prop="trigger_args"
:rules="[{ required: true, message: '请输入Cron表达式', trigger: 'change' }]"
:rules="[
{
required: true,
message: '请输入Cron表达式',
trigger: 'change',
},
]"
style="width: 40%"
>
<el-popover
@@ -411,7 +466,12 @@
@click="openCron = true"
/>
</template>
<vue3CronPlus @change="handlechangeCron" @close="openCron = false" max-height="500px" i18n="cn"></vue3CronPlus>
<vue3CronPlus
@change="handlechangeCron"
@close="openCron = false"
max-height="500px"
i18n="cn"
></vue3CronPlus>
</el-popover>
</el-form-item>
<!-- 开始日期和结束日期 -->
@@ -419,7 +479,9 @@
v-if="formData.trigger && formData.trigger != 'date'"
label="开始日期"
prop="start_date"
:rules="[{ required: false, message: '请选择开始日期', trigger: 'blur' }]"
:rules="[
{ required: false, message: '请选择开始日期', trigger: 'blur' },
]"
style="width: 40%"
>
<el-date-picker
@@ -434,7 +496,9 @@
v-if="formData.trigger && formData.trigger != 'date'"
label="结束日期"
prop="end_date"
:rules="[{ required: false, message: '请选择结束日期', trigger: 'blur' }]"
:rules="[
{ required: false, message: '请选择结束日期', trigger: 'blur' },
]"
style="width: 40%"
>
<el-date-picker
@@ -475,7 +539,6 @@
</div>
</template>
</el-dialog>
</div>
</template>
@@ -488,8 +551,8 @@ defineOptions({
import JobAPI, { JobTable, JobForm, JobPageQuery } from "@/api/monitor/job";
import IntervalTab from "@/components/IntervalTab/index.vue";
import { useDictStore } from "@/store/index";
import { vue3CronPlus } from 'vue3-cron-plus'
import 'vue3-cron-plus/dist/index.css' //
import { vue3CronPlus } from "vue3-cron-plus";
import "vue3-cron-plus/dist/index.css"; //
const dictStore = useDictStore();
@@ -575,9 +638,9 @@ const rules = reactive({
});
//
async function handleRefresh () {
async function handleRefresh() {
await loadingData();
};
}
//
async function loadingData() {
@@ -714,43 +777,41 @@ async function handleExport() {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
const body = {
...queryFormData,
page_no: 1,
page_size: total.value,
};
ElMessage.warning("正在导出数据,请稍候...");
})
.then(async () => {
try {
loading.value = true;
const response = await JobAPI.exportJob(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]);
}
ElMessage.warning("正在导出数据,请稍候...");
const response = await JobAPI.exportJob(queryFormData);
const fileData = response.data;
const fileName = decodeURI(response.headers["content-disposition"].split(";")[1].split("=")[1]);
const fileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
//
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
ElMessage.success('导出成功');
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
} catch (error: any) {
console.error("导出错误:", error);
} finally {
loading.value = false;
}
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) {
console.error("导出错误:", error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.catch(() => {
ElMessageBox.close();
});
}
function handleIntervalConfirm(interval: string) {
@@ -760,7 +821,7 @@ function handleIntervalConfirm(interval: string) {
const handlechangeCron = (cronStr: string) => {
// formData.trigger_args = cronStr;
if (typeof (cronStr) == "string") {
if (typeof cronStr == "string") {
formData.trigger_args = cronStr;
}
};
@@ -771,20 +832,22 @@ const handleClear = () => {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await JobAPI.clearJob();
ElMessage.success("清空成功");
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await JobAPI.clearJob();
ElMessage.success("清空成功");
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
};
// : 1: 2: 3:
@@ -105,11 +105,12 @@ import { LocationQuery, RouteLocationRaw, useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import AuthAPI, {type LoginFormData, type CaptchaInfo } from "@/api/system/auth";
import router from "@/router";
import { useUserStore } from "@/store";
import { useAppStore, useUserStore } from "@/store";
import CommonWrapper from "@/components/CommonWrapper/index.vue";
const { t } = useI18n();
const userStore = useUserStore();
const appStore = useAppStore();
const route = useRoute();
@@ -203,6 +204,10 @@ async function handleLoginSubmit() {
// 4. token:
// - "": tokenlocalStorage
// - "": tokensessionStorage
//
appStore.showGuide(true);
} catch (error) {
// 5.
getCaptcha(); //
+19 -21
View File
@@ -397,30 +397,28 @@ async function handleExport() {
}).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 response = await ConfigAPI.exportConfig(queryFormData);
const fileData = response.data;
const fileName = decodeURI(response.headers["content-disposition"].split(";")[1].split("=")[1]);
const fileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
//
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();
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
ElMessage.success('导出成功');
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
@@ -260,7 +260,7 @@ const queryFormData = reactive<DictDataPageQuery>({
page_no: 1,
page_size: 10,
dict_label: undefined,
dict_type: undefined,
dict_type: props.dictType,
status: undefined,
start_time: undefined,
end_time: undefined,
@@ -309,7 +309,6 @@ async function loadingData() {
loading.value = true;
try {
// dictType
queryFormData.dict_type = props.dictType;
const response = await DictAPI.getDictDataList(queryFormData);
pageTableData.value = response.data.data.items;
total.value = response.data.data.total;
@@ -440,30 +439,28 @@ async function handleExport() {
}).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 response = await DictAPI.exportDictData(queryFormData);
const fileData = response.data;
const fileName = decodeURI(response.headers["content-disposition"].split(";")[1].split("=")[1]);
const fileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
//
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();
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
ElMessage.success('导出成功');
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
+19 -21
View File
@@ -419,30 +419,28 @@ async function handleExport() {
}).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 response = await DictAPI.exportDictType(queryFormData);
const fileData = response.data;
const fileName = decodeURI(response.headers["content-disposition"].split(";")[1].split("=")[1]);
const fileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
//
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();
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
ElMessage.success('导出成功');
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
+19 -21
View File
@@ -355,30 +355,28 @@ async function handleExport() {
}).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 response = await LogAPI.exportLog(queryFormData);
const fileData = response.data;
const fileName = decodeURI(response.headers["content-disposition"].split(";")[1].split("=")[1]);
const fileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
//
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();
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
ElMessage.success('导出成功');
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
+19 -21
View File
@@ -445,30 +445,28 @@ async function handleExport() {
}).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 response = await NoticeAPI.exportNotice(queryFormData);
const fileData = response.data;
const fileName = decodeURI(response.headers["content-disposition"].split(";")[1].split("=")[1]);
const fileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
//
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();
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
ElMessage.success('导出成功');
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
+19 -21
View File
@@ -397,30 +397,28 @@ async function handleExport() {
}).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 response = await PositionAPI.exportPosition(queryFormData);
const fileData = response.data;
const fileName = decodeURI(response.headers["content-disposition"].split(";")[1].split("=")[1]);
const fileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
//
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();
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
ElMessage.success('导出成功');
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
+21 -23
View File
@@ -248,10 +248,10 @@ const tableColumns = ref([
])
//
const detailFormData = ref<RoleTable>({});
const detailFormData = ref<RoleTable>({} as RoleTable);
//
const checkedRole = ref<RoleTable>({});
const checkedRole = ref<RoleTable>({} as RoleTable);
const queryFormData = reactive<TablePageQuery>({
page_no: 1,
@@ -424,30 +424,28 @@ async function handleExport() {
}).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 response = await RoleAPI.exportRole(queryFormData);
const fileData = response.data;
const fileName = decodeURI(response.headers["content-disposition"].split(";")[1].split("=")[1]);
const fileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
//
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();
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
ElMessage.success('导出成功');
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
@@ -14,9 +14,10 @@
<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
格式为*.xlsx / *.xls文件不超过一个
<el-link type="primary" icon="download" underline="never" @click="handleDownloadTemplate">
<div class="el-upload__tip flex">
<el-text type="warning" class="mx-1">注意事项</el-text>
<el-text type="danger" class="mx-1">格式为*.xlsx / *.xls文件不超过 5MB </el-text>
<el-link class="mx-1" type="primary" icon="download" underline="never" @click="handleDownloadTemplate">
下载模板
</el-link>
</div>
@@ -27,9 +28,6 @@
</el-scrollbar>
<template #footer>
<div style="padding-right: var(--el-dialog-padding-primary)">
<el-button v-if="resultData.length > 0" type="primary" @click="handleShowResult">
错误信息
</el-button>
<el-button @click="handleClose"> </el-button>
<el-button
type="primary"
@@ -41,27 +39,6 @@
</div>
</template>
</el-dialog>
<el-dialog v-model="resultVisible" title="导入结果" width="600px">
<el-alert
:title="`导入结果:${invalidCount}条无效数据,${validCount}条有效数据`"
type="warning"
:closable="false"
/>
<el-table :data="resultData" style="width: 100%; max-height: 400px">
<el-table-column prop="index" align="center" width="100" type="index" label="序号" />
<el-table-column prop="message" label="错误信息" width="400">
<template #default="scope">
{{ scope.row }}
</template>
</el-table-column>
</el-table>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleCloseResult">关闭</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
@@ -77,10 +54,6 @@ const importModalVisible = defineModel("modelValue", {
default: false,
});
const resultVisible = ref(false);
const resultData = ref<string[]>([]);
const invalidCount = ref(0);
const validCount = ref(0);
const importFormRef = ref(null);
const uploadRef = ref(null);
@@ -91,15 +64,6 @@ const importFormData = reactive<{
files: [],
});
watch(importModalVisible, (newValue) => {
if (newValue) {
resultData.value = [];
resultVisible.value = false;
invalidCount.value = 0;
validCount.value = 0;
}
});
const importFormRules = {
files: [{ required: true, message: "文件不能为空", trigger: "blur" }],
};
@@ -112,7 +76,7 @@ const handleFileExceed = () => {
//
const handleDownloadTemplate = () => {
UserAPI.downloadTemplate().then((response: any) => {
const fileData = response.data.data;
const fileData = response.data;
const fileName = decodeURI(response.headers["content-disposition"].split(";")[1].split("=")[1]);
const fileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
@@ -137,34 +101,20 @@ const handleUpload = async () => {
ElMessage.warning("请选择文件");
return;
}
try {
const response = await UserAPI.importUser(importFormData.files[0].raw as File);
if (response.data.code === ResultEnum.SUCCESS) {
ElMessage.success("导入成功,导入数据:" + response.data.data.count + "条");
emit("import-success");
handleClose();
} else {
ElMessage.error("上传失败");
resultVisible.value = true;
resultData.value = response.data.data.messageList;
invalidCount.value = response.data.data.invalidCount;
validCount.value = response.data.data.validCount;
const file = importFormData.files[0].raw as File;
const formData = new FormData();
formData.append('file', file);
const response = await UserAPI.importUser(formData);
if (response.data.code === ResultEnum.SUCCESS) {
ElMessage.success(`${response.data.msg}${response.data.data}`);
emit("import-success");
handleClose();
}
} catch (error: any) {
console.error(error);
ElMessage.error("上传失败:" + error);
}
} catch (error: any) {
console.error(error);
ElMessage.error("上传失败:" + error);
}
};
//
const handleShowResult = () => {
resultVisible.value = true;
};
//
const handleCloseResult = () => {
resultVisible.value = false;
};
//
+39 -70
View File
@@ -86,7 +86,7 @@
<el-button type="info" icon="upload" circle @click="handleOpenImportDialog" />
</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="default" icon="refresh" circle @click="handleRefresh" />
@@ -560,77 +560,46 @@ async function handleSubmit() {
});
}
//
async function handleOperation(type: 'import' | 'export') {
if (type === 'import') {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.xlsx, .xls';
input.click();
//
async function handleExport() {
ElMessageBox.confirm('是否确认导出当前查询结果用户数据?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
loading.value = true;
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 UserAPI.importUser(formData);
ElMessage.success('导入成功');
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
}
}
ElMessage.warning('正在导出数据,请稍候...');
const response = await UserAPI.exportUser(queryFormData);
const fileData = response.data;
const fileName = decodeURI(response.headers["content-disposition"].split(";")[1].split("=")[1]);
const fileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
ElMessage.success('导出成功');
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
} 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;
ElMessage.warning('正在导出数据,请稍候...');
UserAPI.exportUser(queryFormData).then((response: any) => {
// const fileData = JSON.stringify(response.data.data;
const fileData = response.data.data;
const fileName = decodeURI(response.headers["content-disposition"].split(";")[1].split("=")[1]);
const fileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
ElMessage.success('导出成功');
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
});
} catch (error: any) {
ElMessage.error('文件处理失败', error.message);
console.error('导出错误:', error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
}
else {
ElMessage.error('未知操作类型');
}
}).catch(() => {
ElMessageBox.close();
});
}
//