运营云公共品牌

This commit is contained in:
沈鸿 2025-05-21 13:44:54 +08:00
parent d485768e8f
commit dd1cd7cf03
17 changed files with 835 additions and 317 deletions

View File

@ -31,6 +31,14 @@ export default defineConfig(({ command, mode }) => {
'Access-Control-Allow-Origin': '*',
},
proxy: {
// 仅 Brand 模块走子应用 DevServer
'/api/brand': {
target: 'http://localhost:9526',
changeOrigin: true,
// 如果想去掉 /api/brand 前缀(比如子应用实际监听的是 /brand/...
// 可以加一个 rewrite
// rewrite: path => path.replace(/^\/api\/brand/, '/brand'),
},
[VITE_APP_BASE_API]: {
target: VITE_APP_BASE_URL,
changeOrigin: true,

View File

@ -0,0 +1,21 @@
// src/apis/brand.js
import axios from '@/utils/axios';
export const getProducts = (params) => {
return axios.get('/api/brand/products', {
params,
apisType: 'mock',
});
};
export const getApplyList = () => {
return axios.get('/api/brand/apply-list', {
apisType: 'mock',
});
};
export const getMonitorList = () => {
return axios.get('/api/brand/monitor-list', {
apisType: 'mock',
});
};

View File

@ -1,23 +0,0 @@
export default {
// 模拟获取商品列表
getProducts: () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ id: 1, name: '耿马绿色蔬菜', imageUrl: 'images/brand/11.png' },
{ id: 2, name: '云南高山茶', imageUrl: 'images/brand/12.png' },
{ id: 3, name: '新疆大枣', imageUrl: 'images/brand/13.png' },
{ id: 4, name: '东北大米', imageUrl: 'images/brand/14.png' },
{ id: 5, name: '山东苹果', imageUrl: 'images/brand/15.png' },
{ id: 6, name: '四川泡菜', imageUrl: 'images/brand/16.png' },
{ id: 7, name: '江苏阳澄湖大闸蟹', imageUrl: 'images/brand/11.png' },
{ id: 8, name: '海南椰子', imageUrl: 'images/brand/12.png' },
{ id: 9, name: '广东早茶', imageUrl: 'images/brand/13.png' },
{ id: 10, name: '北京烤鸭', imageUrl: 'images/brand/14.png' },
{ id: 11, name: '西藏青稞酒', imageUrl: 'images/brand/15.png' },
{ id: 12, name: '青海牦牛肉', imageUrl: 'images/brand/16.png' },
]);
}, 500); // 模拟网络延迟
});
},
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -0,0 +1,136 @@
// src/mock/brand.mock.js
export default [
{
url: '/api/brand/products',
method: 'get',
response: () => ({
code: 200,
data: products,
}),
},
{
url: '/api/brand/apply-list',
method: 'get',
response: () => ({
code: 200,
data: applyData,
}),
},
{
url: '/api/brand/monitor-list',
method: 'get',
response: () => ({
code: 200,
data: monitor,
}),
},
];
const products = [
{
id: 1,
name: '耿马镇沙疆西红柿',
batch: '10021',
duration: '6个月',
expireDate: '2025.01.01',
status: 'authorized',
statusLabel: '已授权',
img: 'images/brand/product4.png',
},
{
id: 2,
name: '孟弄乡沙地土豆',
batch: '10022',
duration: '6个月',
expireDate: '2025.01.02',
status: 'authorized',
statusLabel: '已授权',
img: 'images/brand/product6.png',
},
{
id: 3,
name: '芒洪乡水果彩椒',
batch: '10021',
duration: '6个月',
expireDate: '2025.01.01',
status: 'authorized',
statusLabel: '已授权',
img: 'images/brand/product2.png',
},
{
id: 4,
name: '耿马镇沙疆西红柿',
batch: '10021',
duration: '6个月',
expireDate: '2025.01.01',
status: 'approving',
statusLabel: '审批中',
img: 'images/brand/product4.png',
},
{
id: 5,
name: '孟弄乡沙地土豆',
batch: '10022',
duration: '6个月',
expireDate: '2025.01.02',
status: 'approving',
statusLabel: '审批中',
img: 'images/brand/product6.png',
},
{
id: 6,
name: '芒洪乡水果彩椒',
batch: '10021',
duration: '6个月',
expireDate: '2025.01.01',
status: 'expired',
statusLabel: '已失效',
img: 'images/brand/product2.png',
},
];
const applyData = [
{ id: 1, name: '耿马绿色蔬菜', imageUrl: 'images/brand/11.png' },
{ id: 2, name: '云南高山茶', imageUrl: 'images/brand/12.png' },
{ id: 3, name: '新疆大枣', imageUrl: 'images/brand/13.png' },
{ id: 4, name: '东北大米', imageUrl: 'images/brand/14.png' },
{ id: 5, name: '山东苹果', imageUrl: 'images/brand/15.png' },
{ id: 6, name: '四川泡菜', imageUrl: 'images/brand/16.png' },
{ id: 7, name: '江苏阳澄湖大闸蟹', imageUrl: 'images/brand/11.png' },
{ id: 8, name: '海南椰子', imageUrl: 'images/brand/12.png' },
{ id: 9, name: '广东早茶', imageUrl: 'images/brand/13.png' },
{ id: 10, name: '北京烤鸭', imageUrl: 'images/brand/14.png' },
{ id: 11, name: '西藏青稞酒', imageUrl: 'images/brand/15.png' },
{ id: 12, name: '青海牦牛肉', imageUrl: 'images/brand/16.png' },
];
const monitor = [
{
id: 1,
name: '耿马镇沙疆西红柿',
img: 'images/brand/product4.png',
monthlySales: 999,
stock: 10000,
price: 3.0,
status: 'onSale',
},
{
id: 2,
name: '耿马镇沙疆土豆',
img: 'images/brand/product6.png',
monthlySales: 123,
stock: 5000,
price: 2.5,
status: 'onSale',
},
{
id: 3,
name: '彩椒南瓜混合',
img: 'images/brand/product1.png',
monthlySales: 456,
stock: 8000,
price: 4.2,
status: 'offShelf',
},
// … 更多数据
];

View File

@ -184,17 +184,54 @@ export const constantRoutes = [
path: '/sub-operation-service/brand',
name: 'brand',
component: Layout,
redirect: '/sub-operation-service/brand/index',
redirect: '/sub-operation-service/brand/apply', // 默认页
meta: { title: '公共品牌' },
children: [
{
path: '/sub-operation-service/brand/index',
path: 'apply',
component: () => import('@/views/brand/index.vue'),
name: 'brandMain',
meta: { title: '公共品牌首页' },
children: [
{
path: '',
component: () => import('@/views/brand/components/ApplyList.vue'),
name: 'brandApplyList',
meta: { title: '使用申请' },
},
{
path: ':id',
component: () => import('@/views/brand/components/ApplyDetail.vue'),
name: 'brandApplyDetail',
meta: { title: '产品申请' },
},
],
},
{
path: 'auth',
component: () => import('@/views/brand/index.vue'),
children: [
{
path: '',
component: () => import('@/views/brand/components/Auth.vue'),
name: 'brandAuth',
meta: { title: '授权管理' },
},
],
},
{
path: 'monitor',
component: () => import('@/views/brand/index.vue'),
children: [
{
path: '',
component: () => import('@/views/brand/components/Monitor.vue'),
name: 'brandMonitor',
meta: { title: '使用监管' },
},
],
},
],
},
{
path: '/sub-operation-service/ecommerce',
name: 'ecommerce',

View File

@ -50,6 +50,10 @@ publicAxios.interceptors.request.use(async (config) => {
config.headers['Content-Type'] = config.uploadType;
break;
}
case 'mock': {
config.baseURL = ''; // 不走 VITE_APP_BASE_API直接请求 mock 路径
break;
}
default: {
config.baseURL = VITE_APP_BASE_API;
}

View File

@ -0,0 +1,129 @@
<template>
<div class="container">
<el-breadcrumb separator="·">
<el-breadcrumb-item style="cursor: pointer" @click="backToList">使用申请</el-breadcrumb-item>
<el-breadcrumb-item><a href="#">我要申请</a></el-breadcrumb-item>
</el-breadcrumb>
<img :src="getAssetsFile('images/brand/11.png')" alt="" class="img" />
<el-form label-width="120px" class="form">
<h1 style="margin: 20px 0 20px 50px">请选择溯源农产品申请</h1>
<el-form-item label="检查批次">
<el-select v-model="batch" placeholder="请选择农产品批次" :width="200">
<el-option label="批次A" value="A" />
<el-option label="批次B" value="B" />
</el-select>
</el-form-item>
<el-form-item label="溯源码">
<el-button type="primary" @click="uploadTraceCode">
<template #icon>
<i class="el-icon-upload"></i>
</template>
点击上传
</el-button>
</el-form-item>
<el-form-item label="产品名称">
<el-input v-model="productName" placeholder="自动获取..." disabled />
</el-form-item>
<el-form-item label="检测时间">
<el-input v-model="detectTime" placeholder="自动获取..." disabled />
</el-form-item>
<el-form-item label="检测站">
<el-input v-model="station" placeholder="自动获取..." disabled />
</el-form-item>
<el-form-item label="产地信息">
<el-input v-model="origin" placeholder="自动获取..." disabled />
</el-form-item>
<el-form-item>
<el-button type="primary" style="border-radius: 8px" @click="submit">提交</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import { useRouter, useRoute } from 'vue-router';
import { ref, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import { getAssetsFile } from '@/utils/index.js';
const router = useRouter();
const route = useRoute();
const productId = route.params.id;
const product = ref({ name: '加载中...', id: productId });
onMounted(() => {
//
product.value = {
id: productId,
name: productId == 1 ? '有机苹果' : '绿色蔬菜',
};
});
const batch = ref('');
const productName = ref('');
const detectTime = ref('');
const station = ref('');
const origin = ref('');
function backToList() {
router.push(`/sub-operation-service/brand/apply`);
}
function uploadTraceCode() {
console.log('上传溯源码');
}
function submit() {
console.log('提交申请:', {
product: product.value,
batch: batch.value,
detectTime: detectTime.value,
station: station.value,
origin: origin.value,
});
ElMessage.success('提交成功!');
}
</script>
<style scoped lang="scss">
.container {
padding: 20px;
background-color: #fff;
position: relative;
height: 100%;
:deep(.el-breadcrumb__inner a) {
color: #25bf82;
}
}
.img {
width: 200px;
height: 200px;
position: absolute;
top: 60px;
left: 20px;
}
.form {
margin-top: 20px;
width: 60%;
margin: 0 auto;
}
:deep(.form .el-form-item__content) {
max-width: 300px;
}
:deep(.form .el-input, .form .el-select) {
width: 100%;
max-width: 300px;
}
</style>

View File

@ -3,10 +3,10 @@
<el-col v-for="product in products" :key="product.id" :span="6">
<el-card class="box-card" :body-style="{ padding: '8px', height: '100%' }">
<div class="flex-column">
<img :src="product.img" alt="商品图" class="img" />
<img :src="getAssetsFile(product.imageUrl)" alt="商品图" class="img" />
<div class="flex-1 flex-around">
<p>{{ product.name }}</p>
<el-button type="success" class="button">我要申请</el-button>
<el-button type="success" class="button" @click="gotoApplication(product.id)">我要申请</el-button>
</div>
</div>
</el-card>
@ -16,32 +16,25 @@
<script setup>
import { ref, onMounted } from 'vue';
import productsApi from '@/apis/products';
import { getApplyList } from '@/apis/brand';
import { getAssetsFile } from '@/utils/index.js';
import { useRouter } from 'vue-router';
const router = useRouter();
const url = 'https://via.placeholder.com/182';
const products = ref([]);
//
const fetchProducts = async () => {
try {
const data = await productsApi.getProducts();
const processedData = data.map((item) => {
return {
...item,
img: getAssetsFile(item.imageUrl), //
};
});
getApplyList().then((res) => {
products.value = res.data;
});
products.value = processedData;
} catch (error) {
console.error('获取商品数据失败:', error);
}
};
function gotoApplication(id) {
router.push(`/sub-operation-service/brand/apply/${id}`);
}
//
onMounted(() => {
fetchProducts();
getApplyList;
});
</script>
@ -95,6 +88,7 @@ onMounted(() => {
.button {
width: 96px;
height: 40px;
border-radius: 8px;
}
}
</style>

View File

@ -0,0 +1,390 @@
<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">999 <span></span></div>
<div class="stat-label">授权产品</div>
</div>
</div>
<div class="card-right">
<img :src="getAssetsFile('images/brand/cardLeft.png')" alt="" />
<p>较上月上涨 <span>7</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">999 <span></span></div>
<div class="stat-label">授权产品</div>
</div>
</div>
<div class="card-right">
<img :src="getAssetsFile('images/brand/cardRight.png')" alt="" />
<p>较上月上涨 <span>7</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">
<el-tab-pane label="已授权" name="authorized" />
<el-tab-pane label="审批中" name="approving" />
<el-tab-pane label="已失效" name="expired" />
</el-tabs>
<div class="product-list">
<div v-for="(product, index) in filteredProducts" :key="product.id" class="product-item" :class="{ 'border-top': index > 0 }">
<div class="product-info">
<img class="product-img" :src="product.img" alt="product" />
<div class="product-text">
<span class="product-name">{{ product.name }}</span>
<div class="detail-item">
<label>检测批次</label>
<span>{{ product.batch }}</span>
</div>
<div class="detail-item">
<label>授权期限</label>
<span>{{ product.duration }}</span>
</div>
<div class="detail-item">
<label>到期时间</label>
<span class="text-expire">{{ product.expireDate }}</span>
</div>
</div>
</div>
<div class="product-action">
<el-tag :class="statusClass(activeTab)" :type="statusTypeMap[product.status]">{{ product.statusLabel }}</el-tag>
<el-button
v-if="product.status === 'authorized'"
type="primary"
plain
:icon="Edit"
size="large"
style="border-radius: 8px"
@click="handleCertificate(product)"
>
授权证书
</el-button>
</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>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { getAssetsFile } from '@/utils/index.js';
import { getProducts } from '@/apis/brand';
import { Edit } from '@element-plus/icons-vue';
const activeStatus = ref('authorized');
const statusTypeMap = {
authorized: 'success',
approving: 'warning',
expired: 'danger',
};
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 statusClass = (tab) => {
if (tab === 'approving') return 'text-warning status-tag';
if (tab === 'expired') return 'text-danger status-tag';
return 'text-success status-tag';
};
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;
};
</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: 100%;
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: 1 solid #f000;
}
}
.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%;
font-size: 16px;
display: flex;
justify-content: space-between;
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>

View File

@ -1,202 +0,0 @@
<template>
<div class="auth-management">
<!-- 统计卡片 -->
<el-row :gutter="20" class="mb-24">
<el-col :span="12">
<el-card shadow="hover" class="stat-card">
<div class="stat-icon green-bg"></div>
<div>
<div class="stat-number">999 </div>
<div class="stat-label">授权产品</div>
</div>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="hover" class="stat-card">
<div class="stat-icon red-bg"></div>
<div>
<div class="stat-number text-warning">199 </div>
<div class="stat-label">临期产品</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 状态筛选 -->
<el-tabs v-model="activeStatus" class="mb-24">
<el-tab-pane label="已授权" name="authorized" />
<el-tab-pane label="审批中" name="approving" />
<el-tab-pane label="已失效" name="expired" />
</el-tabs>
<!-- 产品列表 -->
<el-card shadow="never">
<div v-for="(product, index) in products" :key="product.id" class="product-item" :class="{ 'border-top': index > 0 }">
<img class="product-img" src="https://via.placeholder.com/80" alt="product" />
<div class="product-info">
<div class="product-header">
<el-tag :type="statusMap[product.status]">{{ product.statusLabel }}</el-tag>
<span class="product-name">{{ product.name }}</span>
</div>
<div class="product-details">
<div class="detail-item">
<label>检测批次</label>
<span>{{ product.batch }}</span>
</div>
<div class="detail-item">
<label>授权期限</label>
<span>{{ product.duration }}</span>
</div>
<div class="detail-item">
<label>到期时间</label>
<span class="text-expire">{{ product.expireDate }}</span>
</div>
</div>
</div>
<el-button type="primary" plain size="small" @click="handleCertificate(product)"> 授权证书 </el-button>
</div>
</el-card>
</div>
</template>
<script setup>
import { ref } from 'vue';
const activeStatus = ref('authorized');
const statusMap = {
authorized: 'success',
approving: 'warning',
expired: 'danger',
};
const products = ref([
{
id: 1,
name: '耿马镇沙疆西红柿',
batch: '10021',
duration: '6个月',
expireDate: '2025.01.01',
status: 'authorized',
statusLabel: '已授权',
},
{
id: 2,
name: '孟弄乡沙地土豆',
batch: '10022',
duration: '6个月',
expireDate: '2025.01.02',
status: 'authorized',
statusLabel: '已授权',
},
{
id: 3,
name: '芒洪乡水果彩椒',
batch: '10021',
duration: '6个月',
expireDate: '2025.01.01',
status: 'authorized',
statusLabel: '已授权',
},
]);
const handleCertificate = (product) => {
console.log('查看证书:', product);
};
</script>
<style lang="scss" scoped>
.auth-management {
padding: 20px;
.stat-card {
display: flex;
align-items: center;
padding: 20px;
.stat-icon {
width: 50px;
height: 50px;
font-size: 24px;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
}
.green-bg {
background-color: #67c23a;
}
.red-bg {
background-color: #f56c6c;
}
.stat-number {
font-size: 20px;
font-weight: 600;
&.text-warning {
color: #e6a23c;
}
}
.stat-label {
color: #606266;
}
}
.product-item {
display: flex;
align-items: center;
padding: 20px 0;
&.border-top {
border-top: 1px solid #ebeef5;
}
.product-img {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 12px;
margin-right: 20px;
}
.product-info {
flex: 1;
.product-header {
display: flex;
align-items: center;
margin-bottom: 12px;
.product-name {
margin-left: 12px;
font-weight: 500;
font-size: 16px;
}
}
.product-details {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
.detail-item {
label {
color: #909399;
}
.text-expire {
color: #e6a23c;
}
}
}
}
.el-button {
align-self: flex-end;
margin-left: 20px;
}
}
.mb-24 {
margin-bottom: 24px;
}
}
</style>

View File

@ -1,13 +1,12 @@
<template>
<div class="usage-monitor">
<!-- 顶部 TabPane居中显示 -->
<div class="tabs-wrapper">
<el-tabs v-model="activeTab" type="card" class="centered-tabs">
<el-tabs v-model="activeTab" class="tabs-wrapper">
<el-tab-pane label="在售中" name="onSale" />
<el-tab-pane label="未上架" name="offShelf" />
<el-tab-pane label="已失效" name="expired" />
</el-tabs>
</div>
<!-- 列表内容 -->
<div class="list-wrapper">
@ -19,21 +18,18 @@
<div class="item-info">
<div class="item-name">{{ p.name }}</div>
<div class="item-stats">月售 {{ p.monthlySales }} · 库存 {{ p.stock }}</div>
</div>
<!-- 价格 -->
<div class="item-price">¥ {{ p.price }} /kg</div>
<!-- 操作按钮 -->
<div class="item-actions">
<el-button size="small" @click="onInspect(p)">抽查</el-button>
<el-button size="small" type="danger" @click="onRevoke(p)">取消授权</el-button>
</div>
<!-- 右侧状态 -->
<div class="item-buttom">
<div class="item-status" :class="statusClass(activeTab)">
{{ tabLabels[activeTab] }}
</div>
<div class="item-actions">
<el-button size="large" class="button" @click="onInspect(p)">抽查</el-button>
<el-button size="large" class="button" type="danger" @click="onRevoke(p)">取消授权</el-button>
</div>
</div>
</div>
</div>
</div>
@ -41,6 +37,8 @@
<script setup>
import { ref, computed } from 'vue';
import { getAssetsFile } from '@/utils/index.js';
import { getMonitorList } from '@/apis/brand';
const activeTab = ref('onSale');
@ -55,7 +53,7 @@ const products = ref([
{
id: 1,
name: '耿马镇沙疆西红柿',
img: 'https://via.placeholder.com/80',
img: getAssetsFile('images/brand/product4.png'),
monthlySales: 999,
stock: 10000,
price: 3.0,
@ -64,7 +62,7 @@ const products = ref([
{
id: 2,
name: '耿马镇沙疆土豆',
img: 'https://via.placeholder.com/80',
img: getAssetsFile('images/brand/product6.png'),
monthlySales: 123,
stock: 5000,
price: 2.5,
@ -73,7 +71,7 @@ const products = ref([
{
id: 3,
name: '彩椒南瓜混合',
img: 'https://via.placeholder.com/80',
img: getAssetsFile('images/brand/product1.png'),
monthlySales: 456,
stock: 8000,
price: 4.2,
@ -105,42 +103,42 @@ const statusClass = (tab) => {
.usage-monitor {
padding: 20px;
background: #fff;
border-radius: 16px;
height: 100%;
.tabs-wrapper {
width: 100%;
display: flex;
justify-content: center;
margin-bottom: 24px;
align-items: center;
background-color: #fff;
.centered-tabs {
width: 600px; /* 根据实际需要调整宽度 */
.el-tabs__header {
justify-content: center;
}
:deep(.el-tabs__item) {
font-size: 24px;
font-weight: 700;
// border: 1 solid #f000;
}
}
.list-wrapper {
.list-item {
display: grid;
grid-template-columns: 80px 1fr auto auto 80px;
display: flex;
justify-content: start;
align-items: center;
padding: 16px 0;
&.has-border {
border-top: 1px solid #ebeef5;
}
padding: 20px 0;
.item-img {
width: 80px;
height: 80px;
width: 120px;
height: 120px;
object-fit: cover;
border-radius: 8px;
}
.item-info {
padding-left: 16px;
// background-color: #909399;
.item-name {
font-size: 16px;
font-weight: 500;
font-size: 20px;
font-weight: 700;
margin-bottom: 8px;
}
.item-stats {
@ -150,21 +148,37 @@ const statusClass = (tab) => {
}
.item-price {
font-size: 18px;
font-weight: 600;
font-size: 28px;
font-weight: 700;
color: #67c23a;
text-align: right;
padding: 0 16px;
text-align: left;
padding-top: 8px;
}
.item-buttom {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 110px;
flex: 1;
}
.item-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
.button {
font-size: 20px;
font-weight: 400;
border-radius: 8px;
}
}
.item-status {
text-align: right;
font-size: 14px;
font-size: 20px;
font-weight: 700;
&.text-success {
color: #67c23a;
}

View File

@ -3,50 +3,51 @@
<el-container class="brand-layout-container">
<el-aside class="brand-aside-menu">
<!-- 菜单部分 -->
<el-menu v-model:default-active="activeMenu" class="aside-menu" @select="handleMenuSelect">
<el-menu-item index="1">
<el-icon><Document /></el-icon>
<el-menu :default-active="activeMenu" class="aside-menu" @select="handleSelect">
<el-menu-item index="apply">
<img :src="getAssetsFile('images/brand/Apply.png')" class="menu-icon" alt="申请图标" />
<span>使用申请</span>
</el-menu-item>
<el-menu-item index="2">
<el-icon><Lock /></el-icon>
<el-menu-item index="auth">
<img :src="getAssetsFile('images/brand/autho.png')" class="menu-icon" alt="" />
<span>授权管理</span>
</el-menu-item>
<el-menu-item index="3">
<el-icon><Monitor /></el-icon>
<el-menu-item index="monitor">
<img :src="getAssetsFile('images/brand/supervision.png')" class="menu-icon" alt="" />
<span>使用监管</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-main class="brand-main">
<component :is="currentComponent" />
<router-view />
</el-main>
</el-container>
</div>
</template>
<script setup>
import { Document, Lock, Monitor } from '@element-plus/icons-vue';
import { ref, shallowRef } from 'vue';
import ApplyManagement from './components/ApplyManagement.vue';
import AuthManagement from './components/AuthManagement.vue';
import UsageMonitor from './components/UsageMonitor.vue';
//
const activeMenu = ref('1');
const currentComponent = shallowRef(ApplyManagement);
import { computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { getAssetsFile } from '@/utils/index.js';
const router = useRouter();
const route = useRoute();
//
const activeMenu = computed(() => {
const path = route.path;
if (path.includes('/apply')) return 'apply';
if (path.includes('/auth')) return 'auth';
if (path.includes('/monitor')) return 'monitor';
return '';
});
//
const handleMenuSelect = (index) => {
const components = {
1: ApplyManagement,
2: AuthManagement,
3: UsageMonitor,
};
currentComponent.value = components[index];
};
function handleSelect(index) {
router.push(`/sub-operation-service/brand/${index}`);
}
</script>
<style lang="scss" scoped>
@ -69,6 +70,9 @@ const handleMenuSelect = (index) => {
line-height: 48px;
margin: 4px 8px;
border-radius: 4px;
font-size: 20px;
font-weight: 400;
text-align: left;
&:hover {
background-color: #f5f7fa;
}
@ -78,6 +82,12 @@ const handleMenuSelect = (index) => {
font-size: 18px;
vertical-align: middle;
}
.menu-icon {
width: 1.5em;
height: 1.5em;
margin-right: 8px;
vertical-align: -0.15em;
}
span {
vertical-align: middle;
}