496 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<common current-name="mySource">
<template #main>
<div class="container">
<userHeader ref="titleRef" :title="'我的溯源'"></userHeader>
<!-- 搜索栏 -->
<div ref="searchBarRef" class="search-bar">
<div class="search-bar-left">
<el-form ref="searchForm" :inline="true" :model="formInline" class="demo-form-inline" :label-width="'auto'">
<el-form-item label="产品名称" prop="productName">
<el-input v-model="formInline.productName" placeholder="请输入产品名称" clearable />
</el-form-item>
<el-form-item label="溯源码" prop="sourceCode">
<el-input v-model="formInline.sourceCode" placeholder="请输入编号" clearable />
</el-form-item>
<el-form-item label="申请状态" prop="status">
<el-select v-model="formInline.status" placeholder="请选择">
<el-option label="全部" value="99" />
<el-option label="待检测" value="0" />
<el-option label="已检测" value="1" />
</el-select>
</el-form-item>
<el-form-item label="申请日期" prop="startDate" style="margin-right: 0">
<el-date-picker v-model="formInline.startDate" type="date" placeholder="请选择起始日期" clearable style="width: 160px" />
</el-form-item>
<el-form-item style="margin-right: 0">
<span style="width: 30px; text-align: center; display: inline-block"> - </span>
</el-form-item>
<el-form-item prop="endDate">
<el-date-picker v-model="formInline.endDate" type="date" placeholder="请选择截止日期" clearable style="width: 160px" />
</el-form-item>
</el-form>
</div>
<div class="search-bar-right">
<el-button type="primary" icon="Search" @click="onSubmit">查询</el-button>
<el-button icon="Refresh" style="margin: 24px 0 0 0" @click="resetForm">重置</el-button>
</div>
</div>
<!-- 表格 -->
<div class="table-cont" :style="{ height: tableViewportHeight + 'px' }">
<tableComponent
:table-data="tableData"
:columns="columns"
:show-border="false"
:show-selection="true"
:header-cell-class-name="getHeaderClass"
@page-change="handlePaginationChange"
@selection-change="handleSelectionChange"
>
<!-- 自定义-状态 -->
<template #status="slotProps">
<span v-if="slotProps.row.status == 0" class="color-red">待检测</span>
<span v-else-if="slotProps.row.status == 1" class="color-green">已检测</span>
</template>
<!-- 自定义-检测报告 -->
<template #report="slotProps">
<el-icon v-if="slotProps.row.report == 1" color="#25bf82" size="20px" class="el-icon-custom" @click="seeReportImg(slotProps.row)"
><Memo style="width: 2em; height: 2em"
/></el-icon>
<el-icon v-else color="#999" size="20px" class="el-icon-custom"><Memo style="width: 2em; height: 2em" /></el-icon>
</template>
<!-- 自定义-溯源码 -->
<template #sourceCode="slotProps">
<el-icon v-if="slotProps.row.sourceCode == 1" color="#25bf82" size="20px" class="el-icon-custom" @click="seeCodeImg(slotProps.row)"
><Film style="width: 2em; height: 2em"
/></el-icon>
<el-icon v-else color="#999" size="20px" class="el-icon-custom"><Film style="width: 2em; height: 2em" /></el-icon>
</template>
<!-- 自定义-操作 -->
<template #action="slotProps">
<el-dropdown>
<span class="el-dropdown-link">
<el-icon><More /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="handleEdit(slotProps.row, 1)">Action 1</el-dropdown-item>
<el-dropdown-item @click="handleEdit(slotProps.row, 2)">Action 2</el-dropdown-item>
<el-dropdown-item @click="handleEdit(slotProps.row, 3)">Action 3</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</tableComponent>
</div>
<!-- 图片预览组件 -->
<el-image-viewer v-if="showPreview" :url-list="[previewImageUrl]" :initial-index="0" @close="closePreview" />
<!-- 查看溯源码 -->
<el-dialog v-model="dialogVisible" :show-close="false" width="450px" custom-class="traceability-dialog" :before-close="handleClose">
<!-- 主要内容区域 -->
<div class="dialog-content">
<!-- 二维码图片 -->
<img :src="codeUrl" alt="溯源码" class="qrcode-image" />
<!-- 下载区域 -->
<div class="download-section" @click="handleDownload">
<el-icon class="download-icon"><Download /></el-icon>
<span style="margin-left: 5px">下载溯源码</span>
</div>
<!-- 遗传编码区域 -->
<div class="code-section">
<span class="genetic-code">10.5488754215478XE254.1040201</span>
<el-tooltip content="复制溯源码" placement="top">
<el-button color="#25bf82" style="color: #fff; font-weight: 400; height: 100%" @click="copyCode">复制</el-button>
</el-tooltip>
</div>
</div>
<!-- 自定义关闭按钮 -->
<el-icon class="close-button" @click="dialogVisible = false">
<CircleCloseFilled />
</el-icon>
</el-dialog>
</div>
</template>
</common>
</div>
</template>
<script setup>
import common from './components/common.vue';
import { ref, reactive, computed, onMounted, onBeforeUnmount } from 'vue';
import { isEmpty, getAssetsFile } from '@/utils';
import userHeader from './components/userHeader.vue';
import tableComponent from './components/tableComponent.vue';
import Mock from 'mockjs';
import { Close, Download, DocumentCopy, Loading } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
const formInline = reactive({
productName: '',
sourceCode: '',
status: '99',
startDate: '',
endDate: '',
});
const searchForm = ref(null);
const onSubmit = () => {
console.log('submit!');
};
const resetForm = () => {
searchForm.value.resetFields();
};
// 表格数据
const tableData = ref([]);
// 列配置
const columns = ref([
{ prop: 'productName', label: '产品名称', width: '180px' },
{ prop: 'applyTime', label: '申请时间', width: '180px' },
{ prop: 'status', label: '申请状态', slotName: 'status' },
{ prop: 'report', label: '检测报告', slotName: 'report' },
{ prop: 'sourceCode', label: '溯源码', slotName: 'sourceCode' },
{ prop: 'action', label: '操作', slotName: 'action', width: '180' },
]);
// 生成模拟数据
const generateMockData = () => {
return Mock.mock({
'list|30': [
{
'id|+1': 1,
productName: '原生态 有机 西红柿', //产品名称 水果 蔬菜
applyTime: '@datetime',
'status|1': [1, 1, 1, 0], //0待检测 1已检测
'report|1': [1, 1, 1, 0], //0无报告 1有报告
reportUrl: '@url',
'sourceCode|1': [1, 1, 1, 0], //0无溯源码 1有溯源码
code: '@guid',
codeUrl: '@url',
},
],
}).list;
};
// 加载数据
const loadData = () => {
// 模拟API请求延迟
setTimeout(() => {
tableData.value = generateMockData();
}, 500);
};
// 自定义表头类名,也可以在columns指定列中添加headerClassName: 'custom-header'
const getHeaderClass = ({ column }) => {
return 'custom-header';
};
// 分页变化
const handlePaginationChange = ({ page, pageSize }) => {
console.log('分页变化:', page, pageSize);
// 这里可以调用API获取新数据
// fetchData(page, limit,pageSize)
};
// 多选框变化
const handleSelectionChange = (selection) => {
console.log('选中项:', selection);
};
let nowClickRow = ref(null);
const dialogVisible = ref(false);
let codeUrl = ref('');
// 编辑操作
const handleEdit = (row, num) => {
nowClickRow.value = row;
console.log('编辑的行:', row);
console.log('第几个按钮:', num);
};
// 图片预览相关状态
const showPreview = ref(false);
const previewImageUrl = ref('');
// 查看检测报告
const seeReportImg = (row) => {
nowClickRow.value = row;
console.log('查看检测报告:', row);
if (row.reportUrl) {
// 支持相对路径处理
// const imgUrl = row.reportUrl.startsWith('http') ? row.reportUrl : `${import.meta.env.VITE_API_BASE}${row.reportUrl}`;
const imgUrl = getAssetsFile('images/userCenter/检测报告.png').href ?? '';
previewImageUrl.value = imgUrl;
showPreview.value = true;
} else {
ElMessage.warning('该记录没有检测报告');
}
};
const closePreview = () => {
showPreview.value = false;
};
// 查看溯源码
const seeCodeImg = (row) => {
nowClickRow.value = row;
console.log('查看溯源码:', row);
// codeUrl.value = getAssetsFile(row.codeUrl);
codeUrl.value = getAssetsFile('images/userCenter/weweima.png').href ?? '';
dialogVisible.value = true;
};
const handleClose = (done) => {
dialogVisible.value = false;
};
const handleDownload = async () => {
console.log('下载溯源码:', nowClickRow.value);
try {
if (!nowClickRow.value.codeUrl) {
ElMessage.error('图片链接无效');
return;
}
// 添加加载状态提示
const loading = ElMessage({
message: '正在准备下载...',
duration: 0,
icon: Loading,
});
// 使用fetch获取图片Blob数据
const response = await fetch(codeUrl.value);
if (!response.ok) throw new Error('网络响应不正常');
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
// 创建隐藏的下载链接
const link = document.createElement('a');
link.href = blobUrl;
link.download = `${new Date().toISOString().slice(0, 10)}.png`;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
// 清理
setTimeout(() => {
document.body.removeChild(link);
window.URL.revokeObjectURL(blobUrl);
loading.close();
ElMessage.success('下载完成');
}, 100);
} catch (error) {
console.error('下载失败:', error);
ElMessage.error('下载失败: ' + error.message);
}
};
const copyCode = () => {
const code = '10.5488754215478XE254.1040201';
navigator.clipboard
.writeText(code)
.then(() => ElMessage.success('复制成功'))
.catch(() => ElMessage.error('复制失败'));
};
const titleRef = ref(null);
const searchBarRef = ref(null);
const tableViewportHeight = ref(0);
// 精确计算可用高度
const calculateTableHeight = () => {
// 获取窗口总高度
const windowHeight = window.innerHeight;
// 获取各组件高度
const headerHeight = titleRef.value?.$el?.offsetHeight || 0;
const searchBarHeight = searchBarRef.value?.offsetHeight || 0;
// 计算容器内边距补偿(根据实际样式调整)
const paddingCompensation = 60;
// 最终计算
tableViewportHeight.value = windowHeight - headerHeight - searchBarHeight - paddingCompensation;
// console.log(tableViewportHeight.value);
};
// 组件挂载时加载数据
onMounted(() => {
loadData();
calculateTableHeight();
// 添加响应式监听
window.addEventListener('resize', calculateTableHeight);
// 监听DOM变化适用于动态变化的header/searchbar
const observer = new ResizeObserver(calculateTableHeight);
if (titleRef.value?.$el) observer.observe(titleRef.value.$el);
if (searchBarRef.value) observer.observe(searchBarRef.value);
});
onBeforeUnmount(() => {
window.removeEventListener('resize', calculateTableHeight);
});
</script>
<style lang="scss" scoped>
.container {
width: 100%;
overflow: hidden; /* 防止全局滚动条 */
.search-bar {
overflow: hidden;
margin: 16px 0;
padding: 16px 8px 0 16px;
border-radius: 16px;
background: #fff;
display: flex;
flex-shrink: 0; /* 禁止收缩 */
min-height: 100px;
.search-bar-left {
flex: 1;
}
.search-bar-right {
width: 100px;
}
.demo-form-inline {
text-align: left;
}
.el-form--inline .el-form-item {
margin-right: 30px;
}
.demo-form-inline .el-input {
--el-input-width: 160px;
}
.demo-form-inline .el-select {
--el-select-width: 160px;
}
.demo-form-inline .el-date-picker {
--el-select-width: 160px;
}
}
.table-cont {
min-height: 300px;
border-radius: 16px;
overflow: hidden;
background-color: #fff;
position: relative;
.el-icon-custom {
vertical-align: middle;
cursor: pointer;
}
}
/* 自定义弹窗样式 */
.traceability-dialog {
background: transparent;
box-shadow: none;
border-radius: 16px !important;
}
/* 主要内容区域 */
.dialog-content {
display: flex;
flex-direction: column;
align-items: center;
background: white;
border-radius: 8px;
padding: 0 0 20px 0;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
/* 产品信息 */
.product-info {
width: 100%;
display: flex;
justify-content: space-between;
margin-bottom: 20px;
font-size: 14px;
color: #666;
}
.product-name {
font-weight: bold;
color: #333;
}
/* 二维码图片 */
.qrcode-image {
width: 200px;
height: 200px;
margin: 0 auto;
}
/* 下载区域 */
.download-section {
display: flex;
align-items: center;
margin: 0 0 10px 0;
color: #25bf82;
cursor: pointer;
font-size: 12px;
line-height: 18px;
}
.download-icon {
margin-left: 8px;
font-size: 16px;
}
/* 遗传编码区域 */
.code-section {
display: flex;
align-items: center;
margin: 10px 0;
background: rgba(37, 191, 130, 0.1);
border-radius: 4px;
}
.genetic-code {
font-family: monospace;
font-size: 14px;
color: #25bf82;
display: inline-block;
max-width: 250px;
padding: 0 10px;
}
.copy-icon {
margin-left: 10px;
color: #409eff;
cursor: pointer;
font-size: 16px;
}
/* 关闭按钮 */
.close-button {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: -60px;
color: white;
font-size: 30px;
cursor: pointer;
transition: all 0.3s;
}
// 表格中文本的颜色
.color-gray {
color: $color-5a;
}
.color-green {
color: $color-success;
}
.color-red {
color: $color-danger;
}
}
</style>