599 lines
18 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 class="auth-management">
<!-- 统计卡片 -->
<el-row :gutter="20">
<el-col :span="12">
<el-card shadow="hover" style="border-radius: 24px" :body-class="'card-body'">
<div class="card-left">
<img :src="getAssetsFile('images/brand/1532.png')" alt="" />
<div class="card-content flex-1 flex-column">
<div class="stat-number">{{ statisticsData.advent }} <span></span></div>
<div class="stat-label">授权产品</div>
</div>
</div>
<div v-if="statisticsData.adventNum >= 0" class="card-right">
<img :src="getAssetsFile('images/brand/cardLeft.png')" alt="" />
<p>
较上月上涨 <span>{{ statisticsData.adventNum }}</span> 件
</p>
</div>
<div v-else class="card-right">
<img :src="getAssetsFile('images/brand/cardRight.png')" alt="" />
<p>
较上月下降 <span>{{ -statisticsData.adventNum }}</span> 件
</p>
</div>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="hover" style="border-radius: 24px" :body-class="'card-body'">
<div class="card-left">
<img :src="getAssetsFile('images/brand/1533.png')" alt="" />
<div class="card-content flex-1 flex-column">
<div class="stat-number">{{ statisticsData.total }} <span>件</span></div>
<div class="stat-label">临期产品</div>
</div>
</div>
<div v-if="statisticsData.totalNum >= 0" class="card-right">
<img :src="getAssetsFile('images/brand/cardLeft.png')" alt="" />
<p>
较上月上涨 <span>{{ statisticsData.totalNum }}</span> 件
</p>
</div>
<div v-else class="card-right">
<img :src="getAssetsFile('images/brand/cardRight.png')" alt="" />
<p>
较上月下降 <span>{{ -statisticsData.totalNum }}</span> 件
</p>
</div>
</el-card>
</el-col>
</el-row>
<!-- 产品列表 -->
<el-card shadow="hover" style="border-radius: 16px" class="product-card">
<!-- 状态筛选 -->
<el-tabs v-model="activeStatus" class="tabs-wrapper" @tab-change="changeStatus">
<el-tab-pane label="已授权" name="2" />
<el-tab-pane label="审批中" name="1" />
<el-tab-pane label="被驳回" name="3" />
<el-tab-pane label="已失效" name="4" />
</el-tabs>
<div class="product-list">
<div v-for="(product, index) in products" :key="product.id" class="product-item" :class="{ 'border-top': index > 0 }">
<div class="product-info">
<img class="product-img" :src="product.goodsUrl ? product.goodsUrl.split(',')[0] : ''" alt="product" />
<div class="product-text">
<span class="product-name">{{ product.productName }}</span>
<div class="detail-item">
<label>检测批次:</label>
<span>2</span>
</div>
<div class="detail-item">
<label>授权期限:</label>
<span
>{{ product.timeNum
}}{{ product.timeNumUnit === 1 ? '天' : product.timeNumUnit === 2 ? '月' : product.timeNumUnit === 3 ? '年' : '' }}</span
>
</div>
<div class="detail-item">
<label>到期时间:</label>
<span class="text-expire">{{ product.endTime }}</span>
</div>
</div>
</div>
<div class="product-action">
<el-tag v-if="product.status == 2" size="large" class="text-success status-tag" type="success">已授权</el-tag>
<el-tag v-if="product.status == 1" size="large" class="text-warning status-tag" type="warning">审批中</el-tag>
<el-tag v-if="product.status == 3" size="large" class="text-danger status-tag" type="danger">被驳回</el-tag>
<el-tag v-if="product.status == 4" size="large" class="text-info status-tag" type="info">已失效</el-tag>
<div style="display: flex">
<el-button
v-if="product.status == 2"
type="primary"
plain
:icon="Edit"
size="large"
style="border-radius: 8px"
@click="handleCertificate(product)"
>
授权证书
</el-button>
<!-- <el-button size="large" class="button" @click="onInspect(product)">溯源报告</el-button>-->
<!-- <el-button v-if="product.status == 2" size="large" class="button" type="danger" @click="onRevoke(product)">取消授权</el-button>-->
</div>
</div>
</div>
</div>
</el-card>
<!-- 证书预览 -->
<el-dialog v-model="certificateDialogVisible" title="授权证书" width="30%" destroy-on-close>
<div style="display: flex; justify-content: center">
<el-image
:src="currentCertificateImg"
fit="contain"
style="max-width: 600px; width: 80%"
:preview-src-list="[currentCertificateImg]"
:z-index="9999"
/>
</div>
</el-dialog>
<!-- 抽查弹窗 -->
<el-dialog v-model="dialogVisible" width="720px" top="20px" modal="false" :before-close="() => (dialogVisible = false)" title="追溯记录">
<div v-if="traceData" class="trace-record">
<!-- 基本信息 -->
<section class="section">
<h3>基本信息</h3>
<el-row :gutter="12">
<el-col :span="6">产品名称:</el-col><el-col :span="18">{{ traceData.productName }}</el-col> <el-col :span="6">产品数量:</el-col
><el-col :span="18">{{ traceData.quantity }}</el-col> <el-col :span="6">生产经营主体:</el-col
><el-col :span="18">{{ traceData.producer }}</el-col> <el-col :span="6">原产地:</el-col
><el-col :span="18">{{ traceData.origin }}</el-col> <el-col :span="6">生产日期:</el-col
><el-col :span="18">{{ traceData.productionDate }}</el-col> <el-col :span="6">追溯码:</el-col
><el-col :span="18">{{ traceData.traceCode }}</el-col> <el-col :span="6">追溯次数:</el-col
><el-col :span="18">{{ traceData.traceCount }} 次</el-col>
</el-row>
</section>
<!-- 基地信息 -->
<section class="section">
<h3>基地信息</h3>
<el-row :gutter="12">
<el-col :span="6">基地地址:</el-col><el-col :span="18">{{ traceData.base.address }}</el-col> <el-col :span="6">地理位置:</el-col
><el-col :span="18">{{ traceData.base.location }}</el-col> <el-col :span="6">面积:</el-col
><el-col :span="18">{{ traceData.base.area }} 亩</el-col> <el-col :span="6">气候条件:</el-col
><el-col :span="18">{{ traceData.base.climate }}</el-col> <el-col :span="6">土壤类型:</el-col
><el-col :span="18">{{ traceData.base.soil }}</el-col>
</el-row>
</section>
<!-- 农事信息 -->
<section class="section">
<h3>农事信息</h3>
<el-table :data="traceData.farmingRecords" stripe border style="width: 100%">
<el-table-column prop="date" label="日期" width="120" />
<el-table-column prop="operation" label="操作" />
<el-table-column prop="operator" label="作业人" width="120" />
</el-table>
</section>
<!-- 包装信息 -->
<section class="section">
<h3>分拣包装</h3>
<el-row :gutter="12">
<el-col :span="6">包装企业:</el-col><el-col :span="18">{{ traceData.packaging.company }}</el-col> <el-col :span="6">包装类型:</el-col
><el-col :span="18">{{ traceData.packaging.type }}</el-col> <el-col :span="6">包装人:</el-col
><el-col :span="18">{{ traceData.packaging.person }}</el-col> <el-col :span="6">包装时间:</el-col
><el-col :span="18">{{ traceData.packaging.time }}</el-col>
</el-row>
</section>
<!-- 仓储物流 -->
<section class="section">
<h3>仓储物流信息</h3>
<el-row :gutter="12">
<el-col :span="6">存储类型:</el-col><el-col :span="18">{{ traceData.logistics.storageType }}</el-col>
<el-col :span="6">存储温度:</el-col><el-col :span="18">{{ traceData.logistics.temperature }}</el-col>
<el-col :span="6">发货地址:</el-col><el-col :span="18">{{ traceData.logistics.shipFrom }}</el-col> <el-col :span="6">收货地址:</el-col
><el-col :span="18">{{ traceData.logistics.shipTo }}</el-col>
</el-row>
</section>
<!-- 交易信息 -->
<section class="section">
<h3>交易信息</h3>
<el-row :gutter="12">
<el-col :span="6">交易时间:</el-col><el-col :span="18">{{ traceData.trade.time }}</el-col> <el-col :span="6">买家:</el-col
><el-col :span="18">{{ traceData.trade.buyer }}</el-col>
</el-row>
</section>
<!-- 右侧图片 -->
<div class="trace-img">
<img :src="traceData.img" alt="产品图" width="150" />
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { getAssetsFile } from '@/utils/index.js';
import { authList, getProducts } from '@/apis/brand';
import { Edit } from '@element-plus/icons-vue';
import { getStatisticsData } from '@/apis/brand.js';
import { ElMessage, ElMessageBox } from 'element-plus';
import { cancelAuth } from '@/apis/brand.js';
const dialogVisible = ref(false);
const traceData = ref(null);
const activeStatus = ref('2');
const products = ref([
{
id: 1,
name: '耿马镇沙疆西红柿',
batch: '10021',
duration: '6个月',
expireDate: '2025.01.01',
status: 'authorized',
statusLabel: '已授权',
img: getAssetsFile('images/brand/product4.png'),
},
{
id: 2,
name: '孟弄乡沙地土豆',
batch: '10022',
duration: '6个月',
expireDate: '2025.01.02',
status: 'authorized',
statusLabel: '已授权',
img: getAssetsFile('images/brand/product6.png'),
},
{
id: 3,
name: '芒洪乡水果彩椒',
batch: '10021',
duration: '6个月',
expireDate: '2025.01.01',
status: 'authorized',
statusLabel: '已授权',
img: getAssetsFile('images/brand/product2.png'),
},
{
id: 4,
name: '耿马镇沙疆西红柿',
batch: '10021',
duration: '6个月',
expireDate: '2025.01.01',
status: 'approving',
statusLabel: '审批中',
img: getAssetsFile('images/brand/product4.png'),
},
{
id: 5,
name: '孟弄乡沙地土豆',
batch: '10022',
duration: '6个月',
expireDate: '2025.01.02',
status: 'approving',
statusLabel: '审批中',
img: getAssetsFile('images/brand/product6.png'),
},
{
id: 6,
name: '芒洪乡水果彩椒',
batch: '10021',
duration: '6个月',
expireDate: '2025.01.01',
status: 'expired',
statusLabel: '已失效',
img: getAssetsFile('images/brand/product2.png'),
},
]);
const statisticsData = ref({
advent: 817,
adventNum: 13,
total: 125,
totalNum: -5,
});
const onRevoke = (p) => {
ElMessageBox.confirm('是否确认取消该商品的授权?', '注意!', {
confirmButtonText: '确认',
cancelButtonText: '放弃',
type: 'error',
})
.then(() => {
cancelAuth({ id: p.id }).then((res) => {
if (res.code === 200) {
ElMessage({
type: 'success',
message: '已取消授权',
});
getAuthList(2);
}
});
})
.catch(() => {
// ElMessage({
// type: 'info',
// message: 'Delete canceled',
// });
});
};
function onInspect(item) {
// 这里用硬编码模拟请求实际中可换成接口调用getTraceById(id).then(res=> traceData.value = res)
const mocks = [
{
productName: '耿马镇沙疆西红柿',
quantity: '300KG',
producer: '北大荒技术有限公司',
origin: '耿马县孟定镇下坝村',
productionDate: '2025-6-3',
traceCode: '10.5487542154785XSE254.1040201',
traceCount: 30,
base: {
address: '耿马县孟定镇下坝村',
location: '东经102° · 北纬24°',
area: 9000,
climate: '亚热带高原季风型,温和多雨',
soil: '红壤',
},
farmingRecords: [
{ date: '2025/3/14', operation: '播种西红柿种', operator: '李强' },
{ date: '2025/4/2', operation: '施肥 氮肥', operator: '李强' },
{ date: '2025/5/17', operation: '浇水', operator: '李强' },
{ date: '2025/6/14', operation: '采摘', operator: '李强' },
],
packaging: {
company: '瑞禾农产品包装公司',
type: '纸箱',
person: '李明瑞',
time: '2025-1-20 16:27:41',
},
logistics: {
storageType: '冷藏',
temperature: '2°C',
shipFrom: '北京市朝阳区解放路24号',
shipTo: '上海市黄浦区南京路36号',
},
trade: {
time: '2025-4-2 08:13:52',
buyer: '李楠',
},
img: 'images/brand/product4.png',
},
];
traceData.value = mocks[0];
traceData.value.img = item.goodsUrl;
traceData.value.productName = item.productName;
console.log(item);
dialogVisible.value = true;
}
// const filteredProducts = computed(() => products.value.filter((p) => p.status === activeStatus.value));
const certificateDialogVisible = ref(false);
const currentCertificateImg = ref('');
const handleCertificate = (product) => {
console.log('查看证书:', product);
// 假设每个产品都有一个证书图片路径字段,临时写死
currentCertificateImg.value = getAssetsFile('images/brand/sqzs.png'); // 你可以换成真实字段,如 product.certificate
certificateDialogVisible.value = true;
};
const changeStatus = (tab) => {
getAuthList(tab);
};
const getAuthList = (status) => {
products.value = [];
authList({ status: status }).then((res) => {
if (res.code === 200) {
products.value = res.data.records;
for (let i in products.value) {
if (products.value[i].endTime) {
products.value[i].endTime = products.value[i].endTime.split(' ')[0];
}
}
}
});
};
const getStatusData = () => {
getStatisticsData().then((res) => {
console.log(res);
});
};
onMounted(() => {
getAuthList(2);
getStatusData();
});
</script>
<style lang="scss" scoped>
.auth-management {
padding: 0 20px 0 0;
height: 100%;
display: flex;
flex-direction: column;
}
.product-card {
flex: 1;
margin-top: 20px;
}
:deep(.el-card__body) {
height: 100%;
}
.product-list {
height: calc(100% - 40px);
overflow: auto;
}
.product-list::-webkit-scrollbar {
display: none;
}
:deep(.card-body) {
display: flex;
align-items: center;
padding: 0;
width: 100%;
height: 100%;
justify-content: space-between;
}
.card-left:deep(img) {
width: 88px;
display: block;
aspect-ratio: 1/1;
padding: 20px;
margin: 0;
}
.card-right:deep(img) {
width: 133px;
height: 80px;
display: block;
// padding: 20px;
margin: 0;
padding: 8px;
}
.card-left {
width: 50%;
display: flex;
justify-content: space-between;
align-items: center;
.card-content {
height: 88px;
// background-color: #fafafa;
span {
font-size: 16px;
font-weight: 400;
text-align: left;
color: #000000;
}
}
.stat-number {
font-size: 32px;
font-weight: 600;
font-weight: 700;
text-align: left;
color: #000000;
}
.stat-label {
display: flex;
align-items: center;
font-size: 20px;
font-weight: 400;
text-align: left;
color: #999999;
}
}
.card-right {
display: flex;
flex-direction: column;
align-items: center;
width: 50%;
p {
margin: 0;
font-size: 16px;
font-weight: 400;
text-align: left;
color: #000000;
span {
font-size: 16px;
font-weight: 700;
text-align: left;
color: #25bf82;
}
}
}
.tabs-wrapper {
width: 100%;
display: flex;
align-items: center;
background-color: #fff;
:deep(.el-tabs__item) {
font-size: 24px;
font-weight: 700;
border: 0;
}
}
.flex {
display: flex;
flex-direction: row;
}
.flex-column {
display: flex;
flex-direction: column;
}
.flex-1 {
flex: 1;
}
.flex-space-between {
justify-content: space-between;
}
.product-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
.product-info {
display: flex;
flex: 1;
.product-img {
width: 110px;
aspect-ratio: 1 / 1;
object-fit: cover;
padding: 8px;
}
.product-text {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.product-name {
font-size: 20px;
font-weight: 700;
text-align: left;
color: #000000;
}
.detail-item {
width: 100%;
display: flex;
gap: 10px;
font-size: 16px;
font-weight: 400;
text-align: left;
color: #999999;
span {
color: #000000;
}
}
}
}
.product-action {
width: 200px;
height: 110px;
padding: 8px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: end;
.status-tag {
text-align: right;
font-size: 20px;
font-weight: 700;
.text-success {
color: #67c23a;
}
.text-warning {
color: #e6a23c;
}
.text-danger {
color: #f56c6c;
}
}
}
}
</style>