公共品牌修改接口以及页面

This commit is contained in:
姚俊旭 2025-06-13 14:26:02 +08:00
parent f9b2977dbd
commit 7fcb280a51
9 changed files with 191 additions and 210 deletions

View File

@ -39,6 +39,12 @@ export function authList(params) {
params,
});
}
export function usageList(params) {
return request('brand/applicationrecord/pageByGoodsStatus', {
method: 'GET',
params,
});
}
export function authDetail(id, params) {
return request(`brand/brandbase/detail/${id}`, {
method: 'GET',
@ -51,6 +57,18 @@ export function getStatisticsData(id, params) {
params,
});
}
export function brandUsingSystem(params) {
return request('brand/brandUsingSystem/viewPage', {
method: 'GET',
params,
});
}
export function brandUsingSystemDetail(id, params) {
return request(`brand/brandUsingSystem/detail/${id}`, {
method: 'GET',
params,
});
}
export function saveRecords(data) {
return request('brand/applicationrecord/save', {

View File

@ -502,6 +502,21 @@ export const constantRoutes = [
},
],
},
{
path: 'monitor',
component: BrandLayout,
meta: { title: '使用监管' },
name: 'authUse',
redirect: '/sub-operation-service/brand/monitor',
children: [
{
path: '',
component: () => import('@/views/brand/components/Monitor.vue'),
meta: { title: '使用监管', hideInBread: true },
name: 'brandMonitor',
},
],
},
],
},

View File

@ -62,7 +62,7 @@
<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" alt="product" />
<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">
@ -99,8 +99,8 @@
>
授权证书
</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>
<!-- <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>

View File

@ -4,24 +4,17 @@
<!-- 制度列表 -->
<div class="system-list">
<div v-for="(item, index) in filteredList" :key="index" class="system-card" @click="showDetail(item)">
<div v-for="(item, index) in systemList" :key="index" class="system-card" @click="showDetail(item)">
<div class="card-header">
<div class="card-title">
<i class="el-icon-document"></i>
{{ item.title }}
</div>
<div class="card-meta">发布日期 {{ item.effectiveDate }}</div>
<!-- <div class="card-meta">-->
<!-- <span>编号{{ item.code }}</span>-->
<!-- </div>-->
<div class="card-meta">发布日期 {{ item.createTime ? item.createTime.split(' ')[0] : '' }}</div>
</div>
<div class="card-content">
<p>{{ item.desc }}</p>
<p>{{ item.descStr }}</p>
</div>
<!-- <div class="card-footer">-->
<!-- <span :class="`status-tag ${item.status === '生效中' ? 'active' : 'inactive'}`">{{ item.status }}</span>-->
<!-- <span>生效日期{{ item.effectiveDate }}</span>-->
<!-- </div>-->
</div>
</div>
@ -34,7 +27,7 @@
<h3>{{ currentDetail.title }}</h3>
<div class="dialog-meta">
<!-- <span>编号{{ currentDetail.code }}</span>-->
<span :class="`status-tag ${currentDetail.status === '生效中' ? 'active' : 'inactive'}`">{{ currentDetail.status }}</span>
<!-- <span :class="`status-tag ${currentDetail.status === 1 ? 'active' : 'inactive'}`">{{ // currentDetail.status === 1 ? '' }}</span>-->
</div>
</div>
<div class="dialog-actions">
@ -45,7 +38,16 @@
</div>
<div class="dialog-content">
<!-- 使用Markdown-it渲染的HTML内容 -->
<div class="doc-preview" v-html="currentDetail.content"></div>
<!-- <div class="doc-preview" v-html="currentDetail.content"></div>-->
<div class="doc-preview">
<iframe
v-if="currentDetail.files && currentDetail.files.length > 0"
:src="`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(currentDetail.files ? currentDetail.files[0].fileUrl : '')}`"
width="100%"
height="600px"
frameborder="0"
></iframe>
</div>
</div>
<div class="dialog-footer">
<div class="footer-info">
@ -60,8 +62,10 @@
</template>
<script setup>
import { ref, computed } from 'vue';
import { ref, computed, onMounted } from 'vue';
import { ElMessage, ElButton } from 'element-plus';
import { brandUsingSystem } from '@/apis/brand.js';
import { brandUsingSystemDetail } from '../../../apis/brand.js';
//
const filterTypes = [
@ -84,76 +88,7 @@ const systemList = ref([
title: '《耿马县蔬菜公共品牌申请管理制度》',
code: 'BRM-2023-001',
desc: '包含品牌标识、使用规范、VI应用等内容的最新版本',
content: `\`<div style="font-family: 'Microsoft YaHei', sans-serif; line-height: 1; color: #333; max-width: 800px; margin: 0 auto; padding: 20px;">
<div style="text-align: center; font-size: 22px; font-weight: bold; color: #2a5885; border-bottom: 2px solid #2a5885; padding-bottom: 10px;">
耿马县蔬菜公共品牌申请管理制度
</div>
<div style="font-size: 18px; font-weight: bold; margin: 5px 0 15px 0; color: #2a5885; border-bottom: 1px solid #eee; padding-bottom: 5px;">
第一章 总则
</div>
<div style="">
<span style="font-weight: bold; margin-right: 5px; color: #d35400;">第一条</span>
为规范"耿马蔬菜"或其他确定的公共品牌名称公共品牌的使用与管理维护品牌形象提升耿马县蔬菜产品的市场竞争力和附加值促进蔬菜产业高质量发展特制定本制度
</div>
<div style="">
<span style="font-weight: bold; margin-right: 5px; color: #d35400;">第二条</span>
本制度所称"耿马蔬菜"公共品牌以下简称"公共品牌"是指经耿马县人民政府授权由耿马县农业农村局或其他指定主管部门统一注册管理和监督代表耿马县特定区域特定品质蔬菜产品的区域性公用品牌标识
</div>
<div style="">
<span style="font-weight: bold; margin-right: 5px; color: #d35400;">第三条</span>
本制度适用于在耿马县行政区域内从事蔬菜生产加工经营自愿申请使用公共品牌的企业农民专业合作社家庭农场等主体以下简称"申请人"
</div>
<div style="">
<span style="font-weight: bold; margin-right: 5px; color: #d35400;">第四条</span>
耿马县农业农村局是公共品牌的主管部门负责品牌的注册推广授权监督管理和违规处理设立或授权专门的管理机构以下简称"品牌管理机构"具体执行日常管理工作
</div>
<div style="font-size: 18px; font-weight: bold; color: #2a5885; border-bottom: 1px solid #eee; padding-bottom: 5px;">
第二章 申请条件
</div>
<div style="">
<span style="font-weight: bold; margin-right: 5px; color: #d35400;">第五条</span>
申请使用公共品牌的申请人应同时具备以下基本条件
<div style="margin-left: 20px; margin-top: 10px;">
<div style="font-weight: bold; color: #2980b9;">主体合法</div>
<div style="margin-left: 10px;">在耿马县依法注册登记具有独立法人资格或规范的经营主体资格经营状况正常</div>
<div style="font-weight: bold; margin-top: 8px; color: #2980b9;">基地在耿马</div>
<div style="margin-left: 10px;">生产加工基地主要位于耿马县行政区域内</div>
<div style="font-weight: bold; margin-top: 8px; color: #2980b9;">质量达标</div>
<div style="margin-left: 10px;">
<ul style="margin-top: 5px; padding-left: 20px;">
<li>产品符合国家行业及地方相关蔬菜产品质量安全标准</li>
<li>建立并有效运行产品质量安全追溯体系</li>
<li>鼓励通过绿色食品有机农产品或良好农业规范GAP等认证</li>
</ul>
</div>
<div style="font-weight: bold; margin-top: 8px; color: #2980b9;">规模与标准</div>
<div style="margin-left: 10px;">具有一定规模的生产基地生产过程执行耿马县制定的蔬菜标准化生产技术规程</div>
<div style="font-weight: bold; margin-top: 8px; color: #2980b9;">信誉良好</div>
<div style="margin-left: 10px;">近两年内无重大产品质量安全环境污染诚信经营等方面的不良记录和行政处罚</div>
</div>
</div>
<!-- 后续章节保持相同格式 -->
<div style="font-size: 18px; font-weight: bold; color: #2a5885; border-bottom: 1px solid #eee; padding-bottom: 5px;">
第六章 附则
</div>
<div style="">
<span style="font-weight: bold; margin-right: 5px; color: #d35400;">第十五条</span>
本制度自发布之日起施行根据实施情况可适时修订
</div>
</div>\``,
content: '',
type: 'management',
file: 'BRM-2023-001.docx',
version: '2.0',
@ -165,21 +100,26 @@ const systemList = ref([
]);
//
const filteredList = computed(() => {
return systemList.value.filter((item) => {
const matchSearch =
item.title.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
item.desc.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
item.code.toLowerCase().includes(searchQuery.value.toLowerCase());
const matchFilter = filterType.value ? item.type === filterType.value : true;
return matchSearch && matchFilter;
});
});
// const filteredList = computed(() => {
// return systemList.value.filter((item) => {
// const matchSearch =
// item.title.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
// item.desc.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
// item.code.toLowerCase().includes(searchQuery.value.toLowerCase());
// const matchFilter = filterType.value ? item.type === filterType.value : true;
// return matchSearch && matchFilter;
// });
// });
//
const showDetail = (item) => {
currentDetail.value = item;
brandUsingSystemDetail(item.id).then((res) => {
if (res.code === 200) {
currentDetail.value = res.data;
}
showDialog.value = true;
});
// currentDetail.value = item;
};
//
@ -187,6 +127,18 @@ const hideDetail = () => {
showDialog.value = false;
};
onMounted(() => {
getDocList();
});
const getDocList = () => {
brandUsingSystem().then((res) => {
if (res.code === 200) {
systemList.value = res.data.records;
console.log(res);
}
});
};
//
const downloadDoc = (item) => {
ElMessage.success({
@ -439,8 +391,6 @@ $color-danger: #ff4949;
white-space: pre-wrap;
line-height: 1.5;
background: #ffffff;
padding: 20px;
border-radius: 8px;
border: 1px solid $color-border;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
font-family: 'Microsoft Yahei', SimSun, sans-serif;
@ -541,7 +491,6 @@ $color-danger: #ff4949;
//
.doc-preview {
line-height: 1.4; //
padding: 12px 16px; //
h1,
h2,

View File

@ -2,7 +2,7 @@
<div class="usage-monitor">
<!-- 顶部 TabPane居中显示 -->
<el-tabs v-model="activeTab" class="tabs-wrapper">
<el-tabs v-model="activeTab" class="tabs-wrapper" @tab-change="changeStatus">
<el-tab-pane label="在售中" name="onSale" />
<el-tab-pane label="未上架" name="offShelf" />
<el-tab-pane label="已失效" name="expired" />
@ -10,15 +10,20 @@
<!-- 列表内容 -->
<div class="list-wrapper">
<div v-for="(p, idx) in filteredProducts" :key="p.id" class="list-item" :class="{ 'has-border': idx > 0 }">
<div v-for="(p, idx) in products" :key="p.id" class="list-item" :class="{ 'has-border': idx > 0 }">
<!-- 商品图 -->
<img class="item-img" :src="p.img" alt="商品图" />
<img class="item-img" :src="p.goodsUrl ? p.goodsUrl.split(',')[0] : ''" alt="商品图" />
<!-- 名称 + 月售/库存 -->
<div class="item-info">
<div class="item-name">{{ p.name }}</div>
<div class="item-stats">月售 {{ p.monthlySales }} · 库存 {{ p.stock }}</div>
<div class="item-price">¥ {{ p.price }} /kg</div>
<div class="item-name">{{ p.apiGoodInfoVoDTO ? p.apiGoodInfoVoDTO.goodName : '' }}</div>
<div class="item-stats">
<span>总销售{{ p.apiGoodInfoVoDTO ? p.apiGoodInfoVoDTO.salesVolume : '' }}</span>
<span style="margin-left: 10px">库存 {{ p.apiGoodInfoVoDTO ? p.apiGoodInfoVoDTO.netWeight[0].goodStock : '' }}</span>
</div>
<div class="item-price">
¥ {{ p.apiGoodInfoVoDTO ? p.apiGoodInfoVoDTO.netWeight[0].goodPrice : '' }} /
{{ p.apiGoodInfoVoDTO ? p.apiGoodInfoVoDTO.netWeight[0].unit : '' }}
</div>
</div>
<div class="item-buttom">
@ -26,8 +31,8 @@
{{ tabLabels[activeTab] }}
</div>
<div class="item-actions">
<el-button size="large" class="button" @click="onInspect(p.id)">抽查</el-button>
<el-button size="large" class="button" type="danger" @click="onRevoke(p)">取消授权</el-button>
<el-button size="large" class="button" @click="onInspect(p)">抽查</el-button>
<el-button v-if="activeTab !== 'expired'" size="large" class="button" type="danger" @click="onRevoke(p)">取消授权</el-button>
</div>
</div>
</div>
@ -105,7 +110,7 @@
<!-- 右侧图片 -->
<div class="trace-img">
<img :src="getAssetsFile(traceData.img)" alt="产品图" />
<img :src="traceData.img" alt="产品图" />
</div>
</div>
</el-dialog>
@ -113,9 +118,12 @@
</template>
<script setup>
import { ref, computed } from 'vue';
import { ref, computed, onMounted } from 'vue';
import { getAssetsFile } from '@/utils/index.js';
import { getMonitorList } from '@/apis/brand';
import Common from '../../farmingService/components/common.vue';
import { usageList, cancelAuth } from '@/apis/brand.js';
import { ElMessage, ElMessageBox } from 'element-plus';
const activeTab = ref('onSale');
const dialogVisible = ref(false);
@ -163,12 +171,12 @@ const products = ref([
const filteredProducts = computed(() => products.value.filter((p) => p.status === activeTab.value));
// id traceData
function onInspect(id) {
console.log('查看产品:', id);
function onInspect(item) {
console.log('查看产品:', item);
// getTraceById(id).then(res=> traceData.value = res)
const mocks = [
{
productName: '耿马镇沙疆西红柿',
productName: '',
quantity: '300KG',
producer: '北大荒技术有限公司',
origin: '耿马县孟定镇下坝村',
@ -183,7 +191,7 @@ function onInspect(id) {
soil: '红壤',
},
farmingRecords: [
{ date: '2025/3/14', operation: '播种西红柿种', operator: '李强' },
{ date: '2025/3/14', operation: '播种', operator: '李强' },
{ date: '2025/4/2', operation: '施肥 氮肥', operator: '李强' },
{ date: '2025/5/17', operation: '浇水', operator: '李强' },
{ date: '2025/6/14', operation: '采摘', operator: '李强' },
@ -204,99 +212,63 @@ function onInspect(id) {
time: '2025-4-2 08:13:52',
buyer: '李楠',
},
img: 'images/brand/product4.png',
},
{
productName: '无土栽培土豆',
quantity: '200KG',
producer: '北大荒技术有限公司',
origin: '耿马县孟定镇下坝村',
productionDate: '2025-1-2',
traceCode: '10.5487542154785XSE254.1040201',
traceCount: 30,
base: {
address: '耿马县孟定镇下坝村',
location: '东经102° · 北纬24°',
area: 12000,
climate: '亚热带高原季风型,温和多雨',
soil: '红壤',
},
farmingRecords: [
{ date: '2025/1/22', operation: '播种 20250102批土豆种', operator: '王岚' },
{ date: '2025/2/14', operation: '施肥 氮肥', operator: '王岚' },
{ date: '2025/3/3', operation: '浇水', operator: '王岚' },
{ date: '2025/4/21', operation: '采摘', operator: '王岚' },
],
packaging: {
company: '瑞禾农产品包装公司',
type: '纸箱',
person: '李桑',
time: '2025-5-2 07:54:14',
},
logistics: {
storageType: '冷藏',
temperature: '2°C',
shipFrom: '北京市朝阳区解放路24号',
shipTo: '上海市黄浦区南京路36号',
},
trade: {
time: '2025-5-16 16:08:35',
buyer: '刘思楠',
},
img: 'images/brand/product6.png',
},
{
productName: '彩椒南瓜混合',
quantity: '200KG',
producer: '北大荒技术有限公司',
origin: '耿马县孟定镇下坝村',
productionDate: '2025-5-15',
traceCode: '10.5487542154785XSE254.1040201',
traceCount: 30,
base: {
address: '耿马县孟定镇下坝村',
location: '东经102° · 北纬24°',
area: 12000,
climate: '亚热带高原季风型,温和多雨',
soil: '红壤',
},
farmingRecords: [
{ date: '2025/2/8', operation: '播种瓜种', operator: '刘琦' },
{ date: '2025/3/14', operation: '施肥 氮肥', operator: '刘琦' },
{ date: '2025/3/20', operation: '浇水', operator: '刘琦' },
{ date: '2025/4/11', operation: '浇水', operator: '刘琦' },
{ date: '2025/5/15', operation: '采摘', operator: '刘琦' },
],
packaging: {
company: '瑞禾农产品包装公司',
type: '纸箱',
person: '王大福',
time: '2025-5-26 14:26:27',
},
logistics: {
storageType: '冷藏',
temperature: '2°C',
shipFrom: '北京市朝阳区解放路24号',
shipTo: '上海市黄浦区南京路36号',
},
trade: {
time: '2025-6-2 11:00:51',
buyer: '刘小花',
},
img: 'images/brand/product1.png',
img: '',
},
];
if (id === 1) {
traceData.value = mocks[0];
} else if (id === 2) {
traceData.value = mocks[1];
} else if (id === 3) {
traceData.value = mocks[2];
}
traceData.value.productName = item.apiGoodInfoVoDTO.goodName;
traceData.value.img = item.goodsUrl ? item.goodsUrl.split(',')[0] : '';
dialogVisible.value = true;
}
const onRevoke = (p) => {
console.log('取消授权', p);
ElMessageBox.confirm('是否确认取消该商品的授权?', '注意!', {
confirmButtonText: '确认',
cancelButtonText: '放弃',
type: 'error',
})
.then(() => {
cancelAuth({ id: p.id }).then((res) => {
if (res.code === 200) {
ElMessage({
type: 'success',
message: '已取消授权',
});
if (activeTab.value === 'onSale') {
getAuthList(2, 1);
} else if (activeTab.value === 'offShelf') {
getAuthList(2, 3);
} else if (activeTab.value === 'expired') {
getAuthList(4, '');
}
}
});
})
.catch(() => {});
};
const changeStatus = (tab) => {
if (tab === 'onSale') {
getAuthList(2, 1);
} else if (tab === 'offShelf') {
getAuthList(2, 3);
} else if (tab === 'expired') {
getAuthList(4, '');
}
};
const getAuthList = (status, isListed) => {
products.value = [];
let obj = {
status: status,
};
if (isListed) {
obj.goodsIsListed = isListed;
}
usageList(obj).then((res) => {
if (res.code === 200) {
products.value = res.data.records;
}
});
};
//
@ -305,6 +277,10 @@ const statusClass = (tab) => {
if (tab === 'expired') return 'text-danger';
return 'text-success';
};
onMounted(() => {
getAuthList(2, 1);
});
</script>
<style scoped lang="scss">

View File

@ -3,7 +3,13 @@
<el-container class="brand-layout-container">
<el-aside class="brand-aside-menu">
<!-- 菜单部分 -->
<el-menu v-model:open="openMenus" :default-active="activeMenu" class="brand-aside-menu" @select="handleSelect">
<el-menu
v-model:open="openMenus"
popper-class="no-transition-menu"
:default-active="activeMenu"
class="brand-aside-menu"
@select="handleSelect"
>
<el-menu-item index="apply">
<img :src="getAssetsFile('images/brand/Apply.png')" class="menu-icon" alt="申请图标" />
<span>使用申请</span>
@ -15,6 +21,7 @@
<span>授权管理</span>
</template>
<el-menu-item index="auth/record"> 授权记录 </el-menu-item>
<el-menu-item index="monitor">使用监管</el-menu-item>
<el-menu-item index="auth/manage"> 品牌使用管理 </el-menu-item>
</el-sub-menu>
@ -34,7 +41,7 @@
</template>
<script setup>
import { ref, computed } from 'vue';
import { ref, computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { getAssetsFile } from '@/utils/index.js';
@ -61,6 +68,20 @@ const activeMenu = computed(() => {
function handleSelect(index) {
router.push(`/sub-operation-service/brand/${index}`);
}
onMounted(() => {
const observer = new MutationObserver((mutations) => {
document.querySelectorAll('.el-menu').forEach((menu) => {
menu.classList.remove('el-menu--popup');
menu.style.transition = 'none';
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
});
</script>
<style lang="scss" scoped>

View File

@ -89,7 +89,8 @@
</el-descriptions-item>
</el-descriptions>
</div>
<div style="display: flex; justify-content: center; flex-wrap: wrap">
<div style="padding: 0 5%; text-indent: 2em; font-size: 18px" v-html="currentGood.describeContent"></div>
<div style="display: flex; justify-content: center; flex-wrap: wrap; margin-top: 20px">
<el-image
v-for="(item, index) in currentGood.detailUrl"
:key="index"

View File

@ -89,7 +89,8 @@
</el-descriptions-item>
</el-descriptions>
</div>
<div style="display: flex; justify-content: center; flex-wrap: wrap">
<div style="padding: 0 5%; text-indent: 2em; font-size: 18px" v-html="currentGood.describeContent"></div>
<div style="display: flex; justify-content: center; flex-wrap: wrap; margin-top: 20px">
<el-image
v-for="(item, index) in currentGood.detailUrl"
:key="index"

View File

@ -91,9 +91,9 @@ const toLink = (n, index) => {
// item.icon = item.icon.replace('-1', '');
// }
// });
console.log('currentIndex.value', currentIndex.value);
// console.log('currentIndex.value', currentIndex.value);
router.push(n.path);
// router.push(n.path);
};
</script>
<style lang="scss" scoped>