599 lines
18 KiB
Vue
Raw Normal View History

2025-05-21 13:44:54 +08:00
<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">
2025-06-12 16:54:13 +08:00
<div class="stat-number">{{ statisticsData.advent }} <span></span></div>
2025-05-21 13:44:54 +08:00
<div class="stat-label">授权产品</div>
</div>
</div>
2025-06-12 16:54:13 +08:00
<div v-if="statisticsData.adventNum >= 0" class="card-right">
2025-05-21 13:44:54 +08:00
<img :src="getAssetsFile('images/brand/cardLeft.png')" alt="" />
2025-06-12 16:54:13 +08:00
<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>
2025-05-21 13:44:54 +08:00
</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">
2025-06-12 16:54:13 +08:00
<div class="stat-number">{{ statisticsData.total }} <span></span></div>
2025-06-09 10:59:28 +08:00
<div class="stat-label">临期产品</div>
2025-05-21 13:44:54 +08:00
</div>
</div>
2025-06-12 16:54:13 +08:00
<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">
2025-05-21 13:44:54 +08:00
<img :src="getAssetsFile('images/brand/cardRight.png')" alt="" />
2025-06-12 16:54:13 +08:00
<p>
较上月下降 <span>{{ -statisticsData.totalNum }}</span>
</p>
2025-05-21 13:44:54 +08:00
</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">
2025-06-11 14:38:40 +08:00
<el-tab-pane label="已授权" name="2" />
<el-tab-pane label="审批中" name="1" />
<el-tab-pane label="被驳回" name="3" />
<el-tab-pane label="已失效" name="4" />
2025-05-21 13:44:54 +08:00
</el-tabs>
<div class="product-list">
<div v-for="(product, index) in products" :key="product.id" class="product-item" :class="{ 'border-top': index > 0 }">
2025-05-21 13:44:54 +08:00
<div class="product-info">
2025-06-13 14:26:02 +08:00
<img class="product-img" :src="product.goodsUrl ? product.goodsUrl.split(',')[0] : ''" alt="product" />
2025-05-21 13:44:54 +08:00
<div class="product-text">
<span class="product-name">{{ product.productName }}</span>
2025-05-21 13:44:54 +08:00
<div class="detail-item">
<label>检测批次</label>
<span>2</span>
2025-05-21 13:44:54 +08:00
</div>
<div class="detail-item">
<label>授权期限</label>
<span
>{{ product.timeNum
}}{{ product.timeNumUnit === 1 ? '天' : product.timeNumUnit === 2 ? '月' : product.timeNumUnit === 3 ? '年' : '' }}</span
>
2025-05-21 13:44:54 +08:00
</div>
<div class="detail-item">
<label>到期时间</label>
<span class="text-expire">{{ product.endTime }}</span>
2025-05-21 13:44:54 +08:00
</div>
</div>
</div>
<div class="product-action">
2025-06-11 14:38:40 +08:00
<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>
2025-06-10 18:19:58 +08:00
<div style="display: flex">
<el-button
2025-06-11 14:38:40 +08:00
v-if="product.status == 2"
2025-06-10 18:19:58 +08:00
type="primary"
plain
:icon="Edit"
size="large"
style="border-radius: 8px"
@click="handleCertificate(product)"
>
授权证书
</el-button>
2025-06-13 14:26:02 +08:00
<!-- <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>-->
2025-06-10 18:19:58 +08:00
</div>
2025-05-21 13:44:54 +08:00
</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>
2025-06-10 18:19:58 +08:00
<!-- 抽查弹窗 -->
<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">
2025-06-11 14:38:40 +08:00
<img :src="traceData.img" alt="产品图" width="150" />
2025-06-10 18:19:58 +08:00
</div>
</div>
</el-dialog>
2025-05-21 13:44:54 +08:00
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
2025-05-21 13:44:54 +08:00
import { getAssetsFile } from '@/utils/index.js';
import { authList, getProducts } from '@/apis/brand';
2025-05-21 13:44:54 +08:00
import { Edit } from '@element-plus/icons-vue';
2025-06-12 16:54:13 +08:00
import { getStatisticsData } from '@/apis/brand.js';
import { ElMessage, ElMessageBox } from 'element-plus';
import { cancelAuth } from '@/apis/brand.js';
2025-05-21 13:44:54 +08:00
2025-06-10 18:19:58 +08:00
const dialogVisible = ref(false);
const traceData = ref(null);
2025-06-11 15:08:27 +08:00
const activeStatus = ref('2');
2025-05-21 13:44:54 +08:00
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'),
},
]);
2025-06-12 16:54:13 +08:00
const statisticsData = ref({
advent: 817,
adventNum: 13,
total: 125,
totalNum: -5,
});
2025-06-10 18:19:58 +08:00
const onRevoke = (p) => {
2025-06-12 16:54:13 +08:00
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',
// });
});
2025-06-10 18:19:58 +08:00
};
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));
2025-05-21 13:44:54 +08:00
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];
}
}
}
});
};
2025-06-12 16:54:13 +08:00
const getStatusData = () => {
getStatisticsData().then((res) => {
console.log(res);
});
};
onMounted(() => {
2025-06-11 14:38:40 +08:00
getAuthList(2);
2025-06-12 16:54:13 +08:00
getStatusData();
});
2025-05-21 13:44:54 +08:00
</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 {
2025-05-22 10:38:36 +08:00
height: calc(100% - 40px);
2025-05-21 13:44:54 +08:00
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;
2025-06-10 18:19:58 +08:00
border: 0;
2025-05-21 13:44:54 +08:00
}
}
.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;
2025-05-21 13:44:54 +08:00
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>