完善导入导出
This commit is contained in:
@@ -47,11 +47,14 @@ export function exportProduct(data: object) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//导入产品
|
//导入产品
|
||||||
export function importProduct(data: object) {
|
export function importProduct(data: FormData) {
|
||||||
return newService({
|
return newService({
|
||||||
url: '/customerService/product/import',
|
url: '/customerService/product/import',
|
||||||
responseType: 'blob',
|
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: data,
|
data: data,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data', // 文件上传需要使用form-data
|
||||||
|
},
|
||||||
|
timeout: 60000, // 文件上传可能较慢
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,3 +8,13 @@ export function getDataList(data: object) {
|
|||||||
params: data,
|
params: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 导出数据
|
||||||
|
export function exportProduct(data: object) {
|
||||||
|
return newService({
|
||||||
|
url: '/customerService/data/statistics/export',
|
||||||
|
responseType: 'blob',
|
||||||
|
method: 'get',
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog title="导入产品" v-model="isShowDialog" width="500px" destroy-on-close @close="handleDialogClose">
|
<el-dialog title="导入产品" v-model="isShowDialog" width="500px" destroy-on-close @close="handleDialogClose">
|
||||||
<div class="import-container">
|
<div class="import-container">
|
||||||
<!-- 文件上传区域 -->
|
<!-- 顶部提示信息 - 只在没有文件时显示 -->
|
||||||
|
<div v-if="!hasFile" class="top-alert mb15">
|
||||||
|
<el-alert title="请先选择要上传的ZIP文件" type="warning" :closable="false" show-icon />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 文件上传区域 - 关键修复:添加 file-list 绑定 -->
|
||||||
<el-upload
|
<el-upload
|
||||||
ref="uploadRef"
|
ref="uploadRef"
|
||||||
class="upload-demo"
|
class="upload-demo"
|
||||||
@@ -16,71 +21,61 @@
|
|||||||
:before-upload="beforeUpload"
|
:before-upload="beforeUpload"
|
||||||
:on-exceed="handleExceed"
|
:on-exceed="handleExceed"
|
||||||
:on-remove="handleRemove"
|
:on-remove="handleRemove"
|
||||||
|
:on-change="handleFileChange"
|
||||||
|
:auto-upload="false"
|
||||||
:show-file-list="true"
|
:show-file-list="true"
|
||||||
:auto-upload="false" <!-- 手动触发上传 -->
|
:file-list="fileList"
|
||||||
>
|
>
|
||||||
<div class="el-upload-area">
|
<el-icon class="el-icon--upload">
|
||||||
<el-icon class="el-icon--upload">
|
<upload-filled />
|
||||||
<UploadFilled />
|
</el-icon>
|
||||||
</el-icon>
|
<div class="el-upload__text">将ZIP文件拖到此处,或<em>点击上传</em></div>
|
||||||
<div class="el-upload__text">将ZIP文件拖到此处,或<em>点击上传</em></div>
|
<template #tip>
|
||||||
<div class="el-upload__tip">上传ZIP文件批量导入产品(TXT格式)</div>
|
<div class="el-upload__tip">支持 .zip 格式,文件大小不超过 {{ fileSizeLimit }}MB</div>
|
||||||
</div>
|
</template>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
|
|
||||||
|
<!-- 文件状态显示 -->
|
||||||
|
<div v-if="hasFile && currentFile" class="file-status mt15">
|
||||||
|
<el-alert :title="`文件准备就绪`" type="success" :closable="false" show-icon>
|
||||||
|
<div>文件已选择: {{ currentFile.name }} ({{ formatFileSize(currentFile.size) }})</div>
|
||||||
|
</el-alert>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 导入说明 -->
|
<!-- 导入说明 -->
|
||||||
<div class="import-instructions mt20">
|
<div class="import-instructions mt20">
|
||||||
<el-card shadow="never" class="box-card">
|
<el-card shadow="never">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>导入说明</span>
|
<span>导入说明</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-text type="info" size="small">
|
<div class="instruction-content">
|
||||||
<ul class="instruction-list">
|
<ul>
|
||||||
<li>1. 请上传包含TXT格式产品数据的ZIP压缩包</li>
|
<li>1. 请先<el-link type="primary" @click="handleDownloadTemplate" :underline="false">下载导入模板</el-link>,查看数据格式</li>
|
||||||
<li>2. 文件编码请使用UTF-8</li>
|
<li>2. 按照模板格式准备您的产品数据</li>
|
||||||
<li>3. 文件大小不能超过 {{ fileSizeLimit }}MB</li>
|
<li>3. 将TXT文件打包成ZIP格式上传</li>
|
||||||
<li>4. 导入前请确保数据格式正确</li>
|
<li>4. 文件编码请使用UTF-8</li>
|
||||||
|
<li>5. 文件大小不能超过 {{ fileSizeLimit }}MB</li>
|
||||||
</ul>
|
</ul>
|
||||||
</el-text>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 导入结果展示 -->
|
|
||||||
<div v-if="importResult" class="import-result mt20">
|
|
||||||
<el-alert
|
|
||||||
:title="importResult.success ? '导入完成' : '导入失败'"
|
|
||||||
:type="importResult.success ? 'success' : 'error'"
|
|
||||||
:closable="false"
|
|
||||||
show-icon
|
|
||||||
>
|
|
||||||
<template v-if="importResult.success">
|
|
||||||
<div>成功导入 {{ importResult.data.failCount === 0 ? '所有' : '部分' }} 产品数据</div>
|
|
||||||
<div v-if="importResult.data.failCount > 0">
|
|
||||||
失败 {{ importResult.data.failCount }} 条记录
|
|
||||||
</div>
|
|
||||||
<div v-if="importResult.data.failReasons && importResult.data.failReasons.length > 0">
|
|
||||||
<el-collapse>
|
|
||||||
<el-collapse-item title="查看失败原因">
|
|
||||||
<div v-for="(reason, index) in importResult.data.failReasons" :key="index">
|
|
||||||
{{ reason }}
|
|
||||||
</div>
|
|
||||||
</el-collapse-item>
|
|
||||||
</el-collapse>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div>{{ importResult.message }}</div>
|
|
||||||
</template>
|
|
||||||
</el-alert>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
|
<el-button type="primary" :loading="downloadLoading" @click="handleDownloadTemplate" class="download-btn">
|
||||||
|
<template #icon>
|
||||||
|
<el-icon><Download /></el-icon>
|
||||||
|
</template>
|
||||||
|
{{ downloadLoading ? '生成中...' : '下载模板' }}
|
||||||
|
</el-button>
|
||||||
|
|
||||||
<el-button @click="handleCancel">取消</el-button>
|
<el-button @click="handleCancel">取消</el-button>
|
||||||
<el-button type="primary" :loading="loading" @click="handleSubmitUpload">
|
|
||||||
|
<!-- 修复:使用更可靠的按钮禁用判断 -->
|
||||||
|
<el-button type="primary" :loading="loading" @click="handleSubmitUpload" :disabled="!canImport || loading">
|
||||||
{{ loading ? '导入中...' : '开始导入' }}
|
{{ loading ? '导入中...' : '开始导入' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
@@ -89,22 +84,30 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive } from 'vue';
|
import { ref, reactive, nextTick, watch, computed } from 'vue';
|
||||||
import { ElMessage, ElMessageBox, type UploadInstance, type UploadFile } from 'element-plus';
|
import { ElMessage, ElMessageBox, type UploadInstance, type UploadFile, type UploadFiles } from 'element-plus';
|
||||||
import { UploadFilled } from '@element-plus/icons-vue';
|
import { UploadFilled, Download } from '@element-plus/icons-vue';
|
||||||
|
import JSZip from 'jszip';
|
||||||
|
import { saveAs } from 'file-saver';
|
||||||
import { importProduct } from '/@/api/customerService/product';
|
import { importProduct } from '/@/api/customerService/product';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'getProductList'): void;
|
(e: 'getRoleList'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
// 响应式状态
|
||||||
const isShowDialog = ref(false);
|
const isShowDialog = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const downloadLoading = ref(false);
|
||||||
const uploadRef = ref<UploadInstance>();
|
const uploadRef = ref<UploadInstance>();
|
||||||
const importResult = ref<any>(null);
|
const importResult = ref<any>(null);
|
||||||
|
const currentFile = ref<File | null>(null);
|
||||||
|
|
||||||
|
// 新增:显式管理文件列表
|
||||||
|
const fileList = ref<UploadFile[]>([]);
|
||||||
|
|
||||||
// 上传配置
|
// 上传配置
|
||||||
const uploadAction = '/api/customerService/product/import';
|
const uploadAction = '/customerService/product/import';
|
||||||
const uploadHeaders = reactive({
|
const uploadHeaders = reactive({
|
||||||
Authorization: `Bearer ${localStorage.getItem('token') || ''}`,
|
Authorization: `Bearer ${localStorage.getItem('token') || ''}`,
|
||||||
});
|
});
|
||||||
@@ -112,95 +115,146 @@ const uploadData = reactive({});
|
|||||||
const acceptFileTypes = '.zip,.ZIP';
|
const acceptFileTypes = '.zip,.ZIP';
|
||||||
const fileSizeLimit = 50;
|
const fileSizeLimit = 50;
|
||||||
|
|
||||||
|
// 修复:使用计算属性判断导入按钮状态
|
||||||
|
const canImport = computed(() => {
|
||||||
|
return fileList.value.length > 0 && fileList.value[0]?.status === 'ready';
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasFile = computed(() => fileList.value.length > 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开对话框
|
* 处理文件变化事件 - 修复版本
|
||||||
|
*/
|
||||||
|
const handleFileChange = (file: UploadFile, files: UploadFile[]) => {
|
||||||
|
console.log('文件变化事件:', file.status, files.length);
|
||||||
|
|
||||||
|
// 更新文件列表
|
||||||
|
fileList.value = files;
|
||||||
|
|
||||||
|
if (file.status === 'ready') {
|
||||||
|
currentFile.value = file.raw;
|
||||||
|
ElMessage.success('文件已选择,可以开始导入');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理文件移除
|
||||||
|
*/
|
||||||
|
const handleRemove = (file: UploadFile, files: UploadFile[]) => {
|
||||||
|
fileList.value = files;
|
||||||
|
currentFile.value = null;
|
||||||
|
importResult.value = null;
|
||||||
|
ElMessage.info('文件已移除');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动提交上传 - 修复版本
|
||||||
|
*/
|
||||||
|
const handleSubmitUpload = async () => {
|
||||||
|
// 使用计算属性进行状态判断
|
||||||
|
if (!canImport.value || !currentFile.value) {
|
||||||
|
ElMessage.warning('请先选择有效的ZIP文件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(`确认导入文件 "${currentFile.value.name}" 吗?`, '导入确认', {
|
||||||
|
confirmButtonText: '确认导入',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
// 直接使用当前文件进行上传
|
||||||
|
await handleCustomUpload(currentFile.value);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('用户取消导入操作');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传前校验 - 优化版本
|
||||||
|
*/
|
||||||
|
const beforeUpload = (file: File): boolean => {
|
||||||
|
console.log('上传前校验:', file.name, file.size);
|
||||||
|
|
||||||
|
const isZip = file.type === 'application/zip' || file.name.toLowerCase().endsWith('.zip');
|
||||||
|
const isLtLimit = file.size / 1024 / 1024 < fileSizeLimit;
|
||||||
|
|
||||||
|
if (!isZip) {
|
||||||
|
ElMessage.error('请上传ZIP格式的压缩包文件!');
|
||||||
|
// 清除无效文件
|
||||||
|
setTimeout(() => {
|
||||||
|
if (uploadRef.value) {
|
||||||
|
uploadRef.value.clearFiles();
|
||||||
|
fileList.value = [];
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLtLimit) {
|
||||||
|
ElMessage.error(`文件大小不能超过${fileSizeLimit}MB!`);
|
||||||
|
setTimeout(() => {
|
||||||
|
if (uploadRef.value) {
|
||||||
|
uploadRef.value.clearFiles();
|
||||||
|
fileList.value = [];
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开对话框 - 修复初始化
|
||||||
*/
|
*/
|
||||||
const openDialog = () => {
|
const openDialog = () => {
|
||||||
isShowDialog.value = true;
|
isShowDialog.value = true;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
downloadLoading.value = false;
|
||||||
importResult.value = null;
|
importResult.value = null;
|
||||||
|
fileList.value = [];
|
||||||
|
currentFile.value = null;
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
if (uploadRef.value) {
|
||||||
|
uploadRef.value.clearFiles();
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理对话框关闭
|
* 处理对话框关闭
|
||||||
*/
|
*/
|
||||||
const handleDialogClose = () => {
|
const handleDialogClose = () => {
|
||||||
uploadRef.value?.clearFiles();
|
if (uploadRef.value) {
|
||||||
importResult.value = null;
|
uploadRef.value.clearFiles();
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 取消操作
|
|
||||||
*/
|
|
||||||
const handleCancel = () => {
|
|
||||||
isShowDialog.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传前校验
|
|
||||||
*/
|
|
||||||
const beforeUpload = (file: File): boolean => {
|
|
||||||
const isZip = file.type === 'application/zip' || file.name.toLowerCase().endsWith('.zip');
|
|
||||||
const isLtLimit = file.size / 1024 / 1024 < fileSizeLimit;
|
|
||||||
|
|
||||||
if (!isZip) {
|
|
||||||
ElMessage.error('请上传ZIP格式的压缩包文件!');
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
fileList.value = [];
|
||||||
if (!isLtLimit) {
|
|
||||||
ElMessage.error(`文件大小不能超过${fileSizeLimit}MB!`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
importResult.value = null;
|
importResult.value = null;
|
||||||
return true;
|
currentFile.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 其余函数保持不变(格式化文件大小、模板下载等)
|
||||||
|
const formatFileSize = (bytes: number): string => {
|
||||||
|
if (bytes === 0) return '0 B';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理文件超出限制
|
|
||||||
*/
|
|
||||||
const handleExceed = () => {
|
const handleExceed = () => {
|
||||||
ElMessage.warning('只能上传一个文件,请先删除当前文件');
|
ElMessage.warning('只能上传一个文件,请先删除当前文件');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const handleCancel = () => {
|
||||||
* 处理文件移除
|
isShowDialog.value = false;
|
||||||
*/
|
|
||||||
const handleRemove = () => {
|
|
||||||
importResult.value = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手动提交上传
|
|
||||||
*/
|
|
||||||
const handleSubmitUpload = async () => {
|
|
||||||
const fileList = uploadRef.value?.uploadFiles;
|
|
||||||
if (!fileList || fileList.length === 0) {
|
|
||||||
ElMessage.warning('请先选择要上传的ZIP文件');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await ElMessageBox.confirm(
|
|
||||||
'确认导入选中的产品数据吗?',
|
|
||||||
'导入确认',
|
|
||||||
{
|
|
||||||
confirmButtonText: '确认导入',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
loading.value = true;
|
|
||||||
importResult.value = null;
|
|
||||||
|
|
||||||
const file = fileList[0].raw!;
|
|
||||||
await handleCustomUpload(file);
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -208,49 +262,190 @@ const handleSubmitUpload = async () => {
|
|||||||
*/
|
*/
|
||||||
const handleCustomUpload = async (file: File) => {
|
const handleCustomUpload = async (file: File) => {
|
||||||
try {
|
try {
|
||||||
// 调用导入接口
|
const formData = new FormData();
|
||||||
const response = await importProduct(file);
|
formData.append('file', file);
|
||||||
|
formData.append('type', 'zip');
|
||||||
|
formData.append('fileName', file.name);
|
||||||
|
|
||||||
// 根据API文档,响应结构为: { failCount: number, failReasons: string[] }
|
const response = await importProduct(formData);
|
||||||
if (response.code === 200) {
|
console.log('导入响应:', response);
|
||||||
|
|
||||||
|
if (response.code === 0 || response.failCount !== undefined) {
|
||||||
|
const resultData = response.data || response;
|
||||||
importResult.value = {
|
importResult.value = {
|
||||||
success: true,
|
success: true,
|
||||||
data: response.data,
|
data: resultData,
|
||||||
message: '导入成功'
|
message: '导入成功',
|
||||||
};
|
};
|
||||||
|
|
||||||
ElMessage.success(`导入完成!失败记录:${response.data.failCount}条`);
|
ElMessage.success(`导入完成!失败记录:${resultData.failCount || 0}条`);
|
||||||
emit('getProductList');
|
emit('getRoleList');
|
||||||
|
|
||||||
// 3秒后自动关闭
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (isShowDialog.value && response.data.failCount === 0) {
|
if (isShowDialog.value) {
|
||||||
isShowDialog.value = false;
|
isShowDialog.value = false;
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.message || '导入失败');
|
throw new Error(response.message || '导入失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
console.error('导入失败:', error);
|
||||||
importResult.value = {
|
importResult.value = {
|
||||||
success: false,
|
success: false,
|
||||||
message: error.message || '导入失败'
|
message: error.message || '导入失败',
|
||||||
};
|
};
|
||||||
ElMessage.error(`导入失败:${error.message || '未知错误'}`);
|
ElMessage.error(`导入失败:${error.message || '未知错误'}`);
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 保留原有方法用于自动上传模式
|
// 模板下载相关函数保持不变
|
||||||
const handleUploadSuccess = (response: any) => {
|
const createTextFile = (content: string, filename: string): Blob => {
|
||||||
// 处理自动上传的成功响应
|
const bom = new Uint8Array([0xef, 0xbb, 0xbf]);
|
||||||
|
const contentBytes = new TextEncoder().encode(content);
|
||||||
|
const blobContent = new Uint8Array(bom.length + contentBytes.length);
|
||||||
|
blobContent.set(bom);
|
||||||
|
blobContent.set(contentBytes, bom.length);
|
||||||
|
return new Blob([blobContent], { type: 'text/plain;charset=utf-8' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUploadError = (error: any) => {
|
const generateTemplateZip = async (): Promise<Blob> => {
|
||||||
// 处理自动上传的错误
|
try {
|
||||||
|
const zip = new JSZip();
|
||||||
|
const templateFolder = zip.folder('产品导入模板');
|
||||||
|
if (templateFolder) {
|
||||||
|
templateFolder.file('产品基本信息模板.txt', createTextFile('产品名称:\n产品详情:', '产品基本信息模板.txt'));
|
||||||
|
// templateFolder.file('使用说明.txt', createTextFile('使用说明', '使用说明.txt'));
|
||||||
|
}
|
||||||
|
return await zip.generateAsync({ type: 'blob', compression: 'DEFLATE' });
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('模板生成失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownloadTemplate = async () => {
|
||||||
|
downloadLoading.value = true;
|
||||||
|
try {
|
||||||
|
const zipBlob = await generateTemplateZip();
|
||||||
|
const filename = `产品导入模板_${new Date().toISOString().split('T')[0].replace(/-/g, '')}.zip`;
|
||||||
|
saveAs(zipBlob, filename);
|
||||||
|
ElMessage.success(`模板下载成功!`);
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error(`模板下载失败:${error.message || '未知错误'}`);
|
||||||
|
} finally {
|
||||||
|
downloadLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传成功回调 - 手动上传模式下不会触发
|
||||||
|
*/
|
||||||
|
const handleUploadSuccess = (response: any, file: UploadFile, fileList: UploadFiles) => {
|
||||||
|
console.log('上传成功回调', response);
|
||||||
|
// 手动上传模式下不会执行到这里
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传失败回调 - 手动上传模式下不会触发
|
||||||
|
*/
|
||||||
|
const handleUploadError = (error: Error, file: UploadFile, fileList: UploadFiles) => {
|
||||||
|
console.error('上传失败回调', error);
|
||||||
|
ElMessage.error('文件上传失败');
|
||||||
|
// 手动上传模式下不会执行到这里
|
||||||
};
|
};
|
||||||
|
|
||||||
defineExpose({ openDialog });
|
defineExpose({ openDialog });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 样式保持不变 */
|
||||||
|
.import-container {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-alert.mb15 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-demo {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt15 {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt20 {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon--upload {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #c0c4cc;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload__text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #606266;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload__text em {
|
||||||
|
color: #409eff;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload__tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-status,
|
||||||
|
.import-instructions {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instruction-content ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 16px;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instruction-content li {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-btn {
|
||||||
|
background-color: #67c23a;
|
||||||
|
border-color: #67c23a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-btn:hover {
|
||||||
|
background-color: #5daf34;
|
||||||
|
border-color: #5daf34;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-upload-dragger) {
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-upload-list) {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -10,26 +10,26 @@
|
|||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button size="default" type="primary" class="ml10" @click="handleSearch">
|
<el-button size="default" type="primary" class="ml10" @click="handleSearch">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<ele-Search />
|
<Search />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
查询
|
查询
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="default" type="success" class="ml10" @click="onOpenAddRole">
|
<el-button size="default" type="success" class="ml10" @click="onOpenAddRole">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<ele-FolderAdd />
|
<FolderAdd />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
新增产品
|
新增产品
|
||||||
</el-button>
|
</el-button>
|
||||||
<!-- 导入导出按钮 -->
|
<!-- 导入导出按钮 -->
|
||||||
<el-button size="default" type="warning" class="ml10" @click="onOpenImport">
|
<el-button size="default" type="warning" class="ml10" @click="onOpenImport">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<ele-Upload />
|
<Upload />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
导入
|
导入
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="default" type="info" class="ml10" @click="onOpenExport" style="background-color: deeppink; border: 0">
|
<el-button size="default" type="info" class="ml10" @click="onOpenExport" style="background-color: deeppink; border: 0">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<ele-Download />
|
<Download />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
导出
|
导出
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -41,6 +41,13 @@
|
|||||||
<el-table :data="tableData.data" style="width: 100%" v-loading="tableData.loading">
|
<el-table :data="tableData.data" style="width: 100%" v-loading="tableData.loading">
|
||||||
<el-table-column type="index" label="序号" width="60" />
|
<el-table-column type="index" label="序号" width="60" />
|
||||||
<el-table-column prop="name" label="产品名称" show-overflow-tooltip />
|
<el-table-column prop="name" label="产品名称" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="rag_doc_id" label="已上传知识库" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.rag_doc_id ? 'success' : 'info'" effect="light">
|
||||||
|
{{ row.rag_doc_id ? '已训练' : '未训练' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="creator" label="创建人" show-overflow-tooltip />
|
<el-table-column prop="creator" label="创建人" show-overflow-tooltip />
|
||||||
<el-table-column prop="updater" label="修改人" show-overflow-tooltip />
|
<el-table-column prop="updater" label="修改人" show-overflow-tooltip />
|
||||||
<el-table-column prop="createdAtString" label="创建时间" show-overflow-tooltip>
|
<el-table-column prop="createdAtString" label="创建时间" show-overflow-tooltip>
|
||||||
@@ -50,16 +57,20 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="updatedAtString" label="修改时间" show-overflow-tooltip>
|
<el-table-column prop="updatedAtString" label="修改时间" show-overflow-tooltip>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ formatTime(row.createdAt) }}
|
{{ formatTime(row.updatedAt) }}
|
||||||
|
<!-- 修正:应该是updatedAt -->
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="220">
|
<el-table-column label="操作" width="220">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
|
<el-button style="color: #67c23a" size="small" text type="primary" @click="onTrainProduct(scope.row)">
|
||||||
|
<el-icon><Mic /></el-icon>训练
|
||||||
|
</el-button>
|
||||||
<el-button style="color: deepskyblue" size="small" text type="primary" @click="onOpenEditRole(scope.row)">
|
<el-button style="color: deepskyblue" size="small" text type="primary" @click="onOpenEditRole(scope.row)">
|
||||||
<el-icon><ele-EditPen /></el-icon>修改
|
<el-icon><EditPen /></el-icon>修改
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="small" text type="primary" @click="onRowDel(scope.row)">
|
<el-button size="small" text type="primary" @click="onRowDel(scope.row)">
|
||||||
<el-icon><ele-DeleteFilled /></el-icon>删除
|
<el-icon><Delete /></el-icon>删除
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -85,12 +96,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, onMounted, nextTick } from 'vue';
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||||
|
import { Search, FolderAdd, Upload, Download, Mic, EditPen, Delete } from '@element-plus/icons-vue';
|
||||||
import EditRole from '/@/views/customerService/product/component/editRole.vue';
|
import EditRole from '/@/views/customerService/product/component/editRole.vue';
|
||||||
import ImportDialog from '/@/views/customerService/product/component/importDialog.vue';
|
import ImportDialog from '/@/views/customerService/product/component/importDialog.vue';
|
||||||
import ExportDialog from '/@/views/customerService/product/component/exportDialog.vue';
|
import ExportDialog from '/@/views/customerService/product/component/exportDialog.vue';
|
||||||
import { deleteProduct, getList, getproductAdd } from '/@/api/customerService/product';
|
import { deleteProduct, getList } from '/@/api/customerService/product';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 产品数据接口
|
* 产品数据接口
|
||||||
@@ -100,38 +112,35 @@ interface ProductData {
|
|||||||
status: number;
|
status: number;
|
||||||
listOrder: number;
|
listOrder: number;
|
||||||
name: string;
|
name: string;
|
||||||
creator: string; // 创建人
|
creator: string;
|
||||||
updater: string; // 修改人
|
updater: string;
|
||||||
remark: string;
|
remark: string;
|
||||||
dataScope: number;
|
dataScope: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
rag_doc_id?: string; // 添加缺失的属性
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询参数接口
|
* 查询参数接口
|
||||||
*/
|
*/
|
||||||
interface QueryParam {
|
interface QueryParam {
|
||||||
name: string; // 产品名称
|
name: string;
|
||||||
roleStatus: string; // 状态
|
roleStatus: string;
|
||||||
pageNum: number; // 页码
|
pageNum: number;
|
||||||
pageSize: number; // 每页大小
|
pageSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表格状态接口
|
* 表格状态接口
|
||||||
*/
|
*/
|
||||||
interface TableState {
|
interface TableState {
|
||||||
data: ProductData[]; // 表格数据
|
data: ProductData[];
|
||||||
total: number; // 总条数
|
total: number;
|
||||||
loading: boolean; // 加载状态
|
loading: boolean;
|
||||||
param: QueryParam; // 查询参数
|
param: QueryParam;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 模拟产品数据
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 响应式状态
|
// 响应式状态
|
||||||
const tableData = reactive<TableState>({
|
const tableData = reactive<TableState>({
|
||||||
data: [],
|
data: [],
|
||||||
@@ -156,8 +165,8 @@ const exportDialogRef = ref<InstanceType<typeof ExportDialog>>();
|
|||||||
const getProductList = async () => {
|
const getProductList = async () => {
|
||||||
try {
|
try {
|
||||||
tableData.loading = true;
|
tableData.loading = true;
|
||||||
|
|
||||||
const res = await getList(tableData.param);
|
const res = await getList(tableData.param);
|
||||||
|
|
||||||
if (res.data?.list) {
|
if (res.data?.list) {
|
||||||
tableData.data = res.data.list;
|
tableData.data = res.data.list;
|
||||||
tableData.total = res.data.total;
|
tableData.total = res.data.total;
|
||||||
@@ -170,7 +179,6 @@ const getProductList = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== 时间处理函数 ====================
|
|
||||||
/**
|
/**
|
||||||
* 格式化时间显示
|
* 格式化时间显示
|
||||||
*/
|
*/
|
||||||
@@ -214,20 +222,28 @@ const formatTime = (time: string | number | Date): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理搜索
|
* 训练产品
|
||||||
*/
|
*/
|
||||||
const handleSearch = () => {
|
const onTrainProduct = async (row: ProductData) => {
|
||||||
tableData.param.pageNum = 1; // 重置到第一页
|
try {
|
||||||
getProductList();
|
await ElMessageBox.confirm(`确定要训练产品:"${row.name}"吗?`, '训练确认', {
|
||||||
|
confirmButtonText: '开始训练',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
|
||||||
|
ElMessage.success(`产品"${row.name}"训练任务已开始,请稍后查看训练结果`);
|
||||||
|
} catch (error) {
|
||||||
|
// 用户取消操作
|
||||||
|
console.log('用户取消训练操作');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理分页变化
|
* 处理搜索
|
||||||
* @param pagination - 分页参数
|
|
||||||
*/
|
*/
|
||||||
const handlePaginationChange = (pagination: { page: number; limit: number }) => {
|
const handleSearch = () => {
|
||||||
tableData.param.pageNum = pagination.page;
|
tableData.param.pageNum = 1;
|
||||||
tableData.param.pageSize = pagination.limit;
|
|
||||||
getProductList();
|
getProductList();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -235,42 +251,32 @@ const handlePaginationChange = (pagination: { page: number; limit: number }) =>
|
|||||||
* 打开新增产品对话框
|
* 打开新增产品对话框
|
||||||
*/
|
*/
|
||||||
const onOpenAddRole = () => {
|
const onOpenAddRole = () => {
|
||||||
if (editRoleRef.value) {
|
editRoleRef.value?.openDialog();
|
||||||
editRoleRef.value.openDialog();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开编辑产品对话框
|
* 打开编辑产品对话框
|
||||||
* @param row - 产品数据
|
|
||||||
*/
|
*/
|
||||||
const onOpenEditRole = (row: ProductData) => {
|
const onOpenEditRole = (row: ProductData) => {
|
||||||
if (editRoleRef.value) {
|
editRoleRef.value?.openDialog(row);
|
||||||
editRoleRef.value.openDialog(row);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开导入对话框
|
* 打开导入对话框
|
||||||
*/
|
*/
|
||||||
const onOpenImport = () => {
|
const onOpenImport = () => {
|
||||||
if (importDialogRef.value) {
|
importDialogRef.value?.openDialog();
|
||||||
importDialogRef.value.openDialog();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开导出对话框
|
* 打开导出对话框
|
||||||
*/
|
*/
|
||||||
const onOpenExport = () => {
|
const onOpenExport = () => {
|
||||||
if (exportDialogRef.value) {
|
exportDialogRef.value?.openDialog(tableData.param.name);
|
||||||
exportDialogRef.value.openDialog(tableData.param.name);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除产品
|
* 删除产品
|
||||||
* @param row - 产品数据
|
|
||||||
*/
|
*/
|
||||||
const onRowDel = async (row: ProductData) => {
|
const onRowDel = async (row: ProductData) => {
|
||||||
try {
|
try {
|
||||||
@@ -279,32 +285,19 @@ const onRowDel = async (row: ProductData) => {
|
|||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
});
|
});
|
||||||
console.log(row.id, 'row');
|
|
||||||
|
|
||||||
// 实际删除操作
|
|
||||||
await deleteProduct({ id: row.id });
|
await deleteProduct({ id: row.id });
|
||||||
|
|
||||||
// 模拟删除成功
|
|
||||||
ElMessage.success('删除成功');
|
ElMessage.success('删除成功');
|
||||||
|
|
||||||
// 刷新列表
|
|
||||||
await getProductList();
|
await getProductList();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 用户取消操作,不处理
|
// 用户取消操作
|
||||||
console.log('用户取消删除操作');
|
console.log('用户取消删除操作');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// 页面加载时初始化数据
|
||||||
* 初始化表格数据
|
|
||||||
*/
|
|
||||||
const initTableData = () => {
|
|
||||||
getProductList();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 生命周期 - 页面加载时初始化数据
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initTableData();
|
getProductList();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -333,7 +326,6 @@ onMounted(() => {
|
|||||||
width: 240px;
|
width: 240px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格操作按钮样式
|
|
||||||
:deep(.el-table) {
|
:deep(.el-table) {
|
||||||
.el-button {
|
.el-button {
|
||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
@@ -344,7 +336,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分页样式
|
|
||||||
:deep(.pagination-container) {
|
:deep(.pagination-container) {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
|
|||||||
99
src/views/customerService/report/component/exportDialog.vue
Normal file
99
src/views/customerService/report/component/exportDialog.vue
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog title="是否导出产品" v-model="isShowDialog" width="400px">
|
||||||
|
<div style="text-align: center; padding: 20px 0">
|
||||||
|
<p style="margin-bottom: 20px; font-size: 14px; color: #606266">确定要导数据吗?</p>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="isShowDialog = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleExport" :loading="loading">导出</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { exportProduct } from '/@/api/customerService/report';
|
||||||
|
|
||||||
|
const isShowDialog = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const exportName = ref(''); // 用于存储产品名称筛选参数
|
||||||
|
|
||||||
|
// 打开对话框,支持传入筛选参数
|
||||||
|
const openDialog = (name?: string) => {
|
||||||
|
isShowDialog.value = true;
|
||||||
|
exportName.value = name || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理导出
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
const params: any = {};
|
||||||
|
if (exportName.value) {
|
||||||
|
params.name = exportName.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用导出接口 - 根据API文档,这里应该返回JSON
|
||||||
|
const response = await exportProduct(params);
|
||||||
|
|
||||||
|
console.log('导出响应:', response);
|
||||||
|
|
||||||
|
// 根据API文档,成功响应是200状态码,返回JSON数据
|
||||||
|
// 但实际后端可能直接返回文件流,所以需要处理两种情况
|
||||||
|
|
||||||
|
if (response instanceof Blob) {
|
||||||
|
// 情况1:后端直接返回文件流(你的实际情况)
|
||||||
|
handleFileDownload(response, 'products_export.zip');
|
||||||
|
} else if (response.data && response.data instanceof Blob) {
|
||||||
|
// 情况2:文件流在data字段中
|
||||||
|
handleFileDownload(response.data, 'products_export.zip');
|
||||||
|
} else if (response.data && response.data.downloadUrl) {
|
||||||
|
// 情况3:返回下载链接(符合API文档规范)
|
||||||
|
window.open(response.data.downloadUrl, '_blank');
|
||||||
|
ElMessage.success('导出任务已提交,请在新窗口下载文件');
|
||||||
|
} else {
|
||||||
|
// 情况4:返回任务ID或其他信息
|
||||||
|
ElMessage.success('导出任务已提交,请稍后查看下载列表');
|
||||||
|
}
|
||||||
|
|
||||||
|
isShowDialog.value = false;
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('导出失败:', error);
|
||||||
|
ElMessage.error(`导出失败:${error.message || '未知错误'}`);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理文件下载
|
||||||
|
const handleFileDownload = (blob: Blob, filename: string) => {
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
|
||||||
|
// 如果有筛选条件,在文件名中体现
|
||||||
|
if (exportName.value) {
|
||||||
|
filename = `products_${exportName.value}_${new Date().getTime()}.zip`;
|
||||||
|
} else {
|
||||||
|
filename = `products_${new Date().getTime()}.zip`;
|
||||||
|
}
|
||||||
|
|
||||||
|
link.download = filename;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
ElMessage.success('导出成功!文件已开始下载');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 暴露方法给父组件使用
|
||||||
|
defineExpose({
|
||||||
|
openDialog,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -73,6 +73,7 @@
|
|||||||
@pagination="dataList"
|
@pagination="dataList"
|
||||||
/>
|
/>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
<expotDialog ref="expotDialogRef"></expotDialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -80,6 +81,7 @@
|
|||||||
import { ref, reactive, onMounted, nextTick } from 'vue';
|
import { ref, reactive, onMounted, nextTick } from 'vue';
|
||||||
import { ElMessageBox, ElMessage, FormInstance } from 'element-plus';
|
import { ElMessageBox, ElMessage, FormInstance } from 'element-plus';
|
||||||
import { getDataList } from '/@/api/customerService/report';
|
import { getDataList } from '/@/api/customerService/report';
|
||||||
|
import expotDialog from '/@/views/customerService/report/component/exportDialog.vue';
|
||||||
|
|
||||||
// 定义接口
|
// 定义接口
|
||||||
interface TableDataRow {
|
interface TableDataRow {
|
||||||
@@ -227,10 +229,11 @@ const resetQuery = (formEl: FormInstance | undefined) => {
|
|||||||
dataList();
|
dataList();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
const expotDialogRef = ref();
|
||||||
// 导出数据
|
// 导出数据
|
||||||
const handleExport = () => {
|
const handleExport = () => {
|
||||||
ElMessage.info('导出功能开发中');
|
// ElMessage.info('导出功能开发中');
|
||||||
|
expotDialogRef.value?.openDialog(tableData.param);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化表格数据
|
// 初始化表格数据
|
||||||
|
|||||||
@@ -4,7 +4,22 @@
|
|||||||
<!-- 搜索和操作区域 -->
|
<!-- 搜索和操作区域 -->
|
||||||
<div class="system-user-search mb15">
|
<div class="system-user-search mb15">
|
||||||
<el-form :inline="true">
|
<el-form :inline="true">
|
||||||
|
<el-form-item label="标签">
|
||||||
|
<el-input size="default" v-model="tableData.param.tag" placeholder="请输入标签" class="w-50 m-2" clearable @keyup.enter="handleSearch" />
|
||||||
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
|
<el-button size="default" type="primary" class="ml10" @click="handleSearch" :loading="tableData.loading">
|
||||||
|
<el-icon>
|
||||||
|
<ele-Search />
|
||||||
|
</el-icon>
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
<el-button size="default" class="ml10" @click="handleReset" :disabled="tableData.loading">
|
||||||
|
<el-icon>
|
||||||
|
<ele-Refresh />
|
||||||
|
</el-icon>
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
<el-button size="default" type="success" @click="handleAdd">
|
<el-button size="default" type="success" @click="handleAdd">
|
||||||
<el-icon><FolderAdd /></el-icon>
|
<el-icon><FolderAdd /></el-icon>
|
||||||
新增话术
|
新增话术
|
||||||
@@ -78,6 +93,7 @@ interface ScriptItem {
|
|||||||
interface TableParams {
|
interface TableParams {
|
||||||
pageNum: number;
|
pageNum: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
|
tag: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TableState {
|
interface TableState {
|
||||||
@@ -96,9 +112,27 @@ const tableData = reactive<TableState>({
|
|||||||
param: {
|
param: {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
|
tag: '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理搜索
|
||||||
|
*/
|
||||||
|
const handleSearch = () => {
|
||||||
|
tableData.param.pageNum = 1; // 搜索时重置到第一页
|
||||||
|
loadTableData();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置查询条件
|
||||||
|
*/
|
||||||
|
const handleReset = () => {
|
||||||
|
// 重新获取数据
|
||||||
|
tableData.param = { pageNum: 1, pageSize: 10, tag: '' };
|
||||||
|
loadTableData();
|
||||||
|
};
|
||||||
|
|
||||||
// ==================== 时间处理函数 ====================
|
// ==================== 时间处理函数 ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user