Merge branch 'dev' of http://47.109.205.240:3000/Web/daimp-front into dev
@ -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,
|
||||
|
21
sub-operation-service/src/apis/brand.js
Normal 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',
|
||||
});
|
||||
};
|
@ -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); // 模拟网络延迟
|
||||
});
|
||||
},
|
||||
};
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
BIN
sub-operation-service/src/assets/images/brand/cardLeft.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
sub-operation-service/src/assets/images/brand/cardRight.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
136
sub-operation-service/src/mock/brand.mock.js
Normal 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',
|
||||
},
|
||||
// … 更多数据
|
||||
];
|
@ -232,17 +232,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',
|
||||
|
@ -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;
|
||||
}
|
||||
|
129
sub-operation-service/src/views/brand/components/ApplyDetail.vue
Normal 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>
|
@ -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>
|
390
sub-operation-service/src/views/brand/components/Auth.vue
Normal 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>
|
@ -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>
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -11,17 +11,26 @@ import { getAssetsFile } from '@/utils';
|
||||
// 您提供的耿马县GeoJSON数据
|
||||
const gengmaGeoJSON = json;
|
||||
|
||||
const today = new Date();
|
||||
const formattedDate = today
|
||||
.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
})
|
||||
.replace(/\//g, '/'); // 确保分隔符是 /
|
||||
|
||||
// 模拟乡镇数据(根据图片中的地名)
|
||||
const towns = ref([
|
||||
{ name: '孟定镇', coord: [99.01, 23.64], weather: '晴', temp: '28℃', icon: 'sunny' },
|
||||
{ name: '勐简乡', coord: [99.24, 23.79], weather: '多云', temp: '26℃', icon: 'cloudy' },
|
||||
{ name: '四排山乡', coord: [99.5, 23.38], weather: '小雨', temp: '24℃', icon: 'rainy' },
|
||||
{ name: '大兴乡', coord: [99.8, 23.76], weather: '多云', temp: '25℃', icon: 'cloudy' },
|
||||
{ name: '耿马镇', coord: [99.42, 23.66], weather: '多云', temp: '26℃', icon: 'cloudy' },
|
||||
{ name: '贺派乡', coord: [99.21, 23.4], weather: '晴', temp: '27℃', icon: 'sunny' },
|
||||
{ name: '芒洪乡', coord: [99.73, 23.59], weather: '阴', temp: '23℃', icon: 'overcast' },
|
||||
{ name: '勐永镇', coord: [99.53, 23.99], weather: '小雨', temp: '22℃', icon: 'rainy' },
|
||||
{ name: '勐撒镇', coord: [99.47, 23.85], weather: '晴', temp: '28℃', icon: 'sunny' },
|
||||
{ id: 0, name: '孟定镇', coord: [99.01, 23.64], weather: '晴', temp: '28℃', icon: 'sunny' },
|
||||
{ id: 1, name: '勐简乡', coord: [99.24, 23.79], weather: '多云', temp: '26℃', icon: 'cloudy' },
|
||||
{ id: 2, name: '四排山乡', coord: [99.5, 23.38], weather: '小雨', temp: '24℃', icon: 'rainy' },
|
||||
{ id: 3, name: '大兴乡', coord: [99.8, 23.76], weather: '多云', temp: '25℃', icon: 'cloudy' },
|
||||
{ id: 4, name: '耿马镇', coord: [99.42, 23.66], weather: '多云', temp: '26℃', icon: 'cloudy' },
|
||||
{ id: 5, name: '贺派乡', coord: [99.21, 23.4], weather: '晴', temp: '27℃', icon: 'sunny' },
|
||||
{ id: 6, name: '芒洪乡', coord: [99.73, 23.59], weather: '阴', temp: '23℃', icon: 'overcast' },
|
||||
{ id: 7, name: '勐永镇', coord: [99.53, 23.99], weather: '小雨', temp: '22℃', icon: 'rainy' },
|
||||
{ id: 8, name: '勐撒镇', coord: [99.47, 23.85], weather: '晴', temp: '28℃', icon: 'sunny' },
|
||||
]);
|
||||
|
||||
const mapContainer = ref(null);
|
||||
@ -40,6 +49,11 @@ function getWeatherIconPath(iconType) {
|
||||
let path = iconMap[iconType] ? iconMap[iconType] : 'images/smartFarm/sunny.png';
|
||||
return getAssetsFile(path);
|
||||
}
|
||||
const emit = defineEmits(['changeMap']);
|
||||
// 显示天气详情弹窗
|
||||
const showWeatherDetail = (data) => {
|
||||
emit('changeMap', { message: data });
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 添加防御性检查
|
||||
@ -53,19 +67,13 @@ onMounted(() => {
|
||||
chart.on('click', (params) => {
|
||||
if (params.componentType === 'series') {
|
||||
// 点击乡镇标记点
|
||||
console.log('点击乡镇:', params.name);
|
||||
showWeatherDetail(params.data);
|
||||
showWeatherDetail(params.name);
|
||||
} else if (params.componentType === 'geo') {
|
||||
// 点击地图区域
|
||||
console.log('点击地图区域:', params.name);
|
||||
showWeatherDetail(params.name);
|
||||
}
|
||||
});
|
||||
|
||||
// 显示天气详情弹窗
|
||||
const showWeatherDetail = (data) => {
|
||||
console.log(data);
|
||||
};
|
||||
|
||||
// 注册地图
|
||||
echarts.registerMap('耿马县', gengmaGeoJSON);
|
||||
|
||||
@ -106,6 +114,7 @@ onMounted(() => {
|
||||
coordinateSystem: 'geo',
|
||||
symbolSize: 30,
|
||||
data: towns.value.map((town) => ({
|
||||
id: town.id,
|
||||
name: town.name,
|
||||
value: [...town.coord, town.temp],
|
||||
weather: town.weather,
|
||||
@ -165,9 +174,9 @@ onMounted(() => {
|
||||
graphic: {
|
||||
type: 'text',
|
||||
left: 20,
|
||||
bottom: 5,
|
||||
bottom: 0,
|
||||
style: {
|
||||
text: '数据更新于: 2025.01.01 08:00:00',
|
||||
text: '数据更新于: ' + formattedDate,
|
||||
fill: '#666',
|
||||
fontSize: 12,
|
||||
},
|
||||
|
@ -26,10 +26,12 @@ const towns = ref([
|
||||
|
||||
const mapContainer = ref(null);
|
||||
|
||||
// 获取天气图标路径
|
||||
// function getWeatherIconPath(iconType) {
|
||||
// return `/images/${iconType}.png`; // 使用绝对路径
|
||||
// }
|
||||
const emit = defineEmits(['changeMap2']);
|
||||
// 显示天气详情弹窗
|
||||
const showDustDetail = (data) => {
|
||||
emit('changeMap2', { message: data });
|
||||
};
|
||||
|
||||
function getWeatherIconPath(iconType) {
|
||||
const iconMap = {
|
||||
sunny: 'images/smartFarm/sunny.png',
|
||||
@ -47,20 +49,12 @@ onMounted(() => {
|
||||
// 添加点击事件监听
|
||||
chart.on('click', (params) => {
|
||||
if (params.componentType === 'series') {
|
||||
// 点击乡镇标记点
|
||||
console.log('点击乡镇:', params.name);
|
||||
showWeatherDetail(params.data);
|
||||
showDustDetail(params.name);
|
||||
} else if (params.componentType === 'geo') {
|
||||
// 点击地图区域
|
||||
console.log('点击地图区域:', params.name);
|
||||
showDustDetail(params.name);
|
||||
}
|
||||
});
|
||||
|
||||
// 显示天气详情弹窗
|
||||
const showWeatherDetail = (data) => {
|
||||
console.log(data);
|
||||
};
|
||||
|
||||
// 注册地图
|
||||
echarts.registerMap('耿马县', gengmaGeoJSON);
|
||||
|
||||
|
@ -6,12 +6,13 @@
|
||||
<el-card shadow="hover" style="border-radius: 16px">
|
||||
<el-row>
|
||||
<el-col :span="10">
|
||||
<map-comp style="height: 300px; width: 100%; border: 0"></map-comp>
|
||||
<map-comp style="height: 300px; width: 100%; border: 0" @change-map="changeMap"></map-comp>
|
||||
</el-col>
|
||||
<el-col :span="1"> </el-col>
|
||||
<el-col :span="13">
|
||||
<div class="location">
|
||||
耿马县·孟定镇<img :src="getAssetsFile('images/smartFarm/location.png')" height="20" style="margin-left: 8px" alt="" />
|
||||
耿马县·{{ currentPosition
|
||||
}}<img :src="getAssetsFile('images/smartFarm/location.png')" height="20" style="margin-left: 8px" alt="" />
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-around">
|
||||
<el-card v-for="(item, index) in weatherData" :key="index" :body-style="{ padding: 0 }" shadow="always" class="weatherCards">
|
||||
@ -99,21 +100,21 @@
|
||||
<img :src="getAssetsFile('images/smartFarm/光照传感器.png')" alt="" />
|
||||
光照
|
||||
</div>
|
||||
<div class="values">2000Lux</div>
|
||||
<div class="values">{{ dustData.light }}</div>
|
||||
</div>
|
||||
<div class="dustData">
|
||||
<div>
|
||||
<img :src="getAssetsFile('images/smartFarm/排风.png')" alt="" />
|
||||
排风
|
||||
</div>
|
||||
<div class="values">15m³/h</div>
|
||||
<div class="values">{{ dustData.wind }}</div>
|
||||
</div>
|
||||
<div class="dustData">
|
||||
<div>
|
||||
<img :src="getAssetsFile('images/smartFarm/蒸腾.png')" alt="" />
|
||||
蒸腾
|
||||
</div>
|
||||
<div class="values">2000Lux</div>
|
||||
<div class="values">{{ dustData.evapotranspiration }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex">
|
||||
@ -122,24 +123,24 @@
|
||||
<img :src="getAssetsFile('images/smartFarm/土壤湿度.png')" alt="" />
|
||||
湿度
|
||||
</div>
|
||||
<div class="values">26%</div>
|
||||
<div class="values">34%</div>
|
||||
<div class="values">{{ dustData.wet.min }}</div>
|
||||
<div class="values">{{ dustData.wet.max }}</div>
|
||||
</div>
|
||||
<div class="dustData">
|
||||
<div>
|
||||
<img :src="getAssetsFile('images/smartFarm/土壤温度.png')" alt="" />
|
||||
温度
|
||||
</div>
|
||||
<div class="values">32℃</div>
|
||||
<div class="values">28℃</div>
|
||||
<div class="values">{{ dustData.temp.min }}</div>
|
||||
<div class="values">{{ dustData.temp.max }}</div>
|
||||
</div>
|
||||
<div class="dustData">
|
||||
<div>
|
||||
<img :src="getAssetsFile('images/smartFarm/空气.png')" alt="" />
|
||||
空气
|
||||
</div>
|
||||
<div class="values">300ppm</div>
|
||||
<div class="values">34%</div>
|
||||
<div class="values">{{ dustData.air.dirt }}</div>
|
||||
<div class="values">{{ dustData.air.wet }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="report">
|
||||
@ -155,7 +156,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 35%">
|
||||
<map-simple style="height: 320px; width: 100%"></map-simple>
|
||||
<map-simple style="height: 320px; width: 100%" @change-map2="changeMap2"></map-simple>
|
||||
</div>
|
||||
<div style="width: 35%">
|
||||
<div style="margin-top: 70px; display: flex; text-align: left">
|
||||
@ -164,11 +165,11 @@
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<span style="font-size: 15px">输入</span>
|
||||
<span class="values">11</span>
|
||||
<span class="values">{{ dustData.pressure.input }}</span>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<span style="font-size: 15px">末端</span>
|
||||
<span class="values">2</span>
|
||||
<span class="values">{{ dustData.pressure.end }}</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
@ -177,11 +178,11 @@
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<span style="font-size: 15px">灌溉</span>
|
||||
<span class="values">18℃</span>
|
||||
<span class="values">{{ dustData.flow.output }}</span>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<span style="font-size: 15px">回液</span>
|
||||
<span class="values">18℃</span>
|
||||
<span class="values">{{ dustData.flow.input }}</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
@ -192,11 +193,11 @@
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<span style="font-size: 15px">PH</span>
|
||||
<span class="values">8</span>
|
||||
<span class="values">{{ dustData.fertilization.ph }}</span>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<span style="font-size: 15px">输入</span>
|
||||
<span class="values">18℃</span>
|
||||
<span class="values">{{ dustData.fertilization.output }}</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
@ -293,13 +294,13 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import Common from './components/common.vue';
|
||||
import * as echarts from 'echarts';
|
||||
import MapComp from '@/views/smartFarm/components/mapComp.vue';
|
||||
import { getAssetsFile } from '@/utils/index.js';
|
||||
import MapSimple from '@/views/smartFarm/components/mapSimple.vue';
|
||||
import ChartsFlow from '@/views/smartFarm/components/charts-flow.vue';
|
||||
import Mock from 'mockjs';
|
||||
|
||||
/* --------------- data --------------- */
|
||||
// #region
|
||||
@ -336,6 +337,67 @@ const weatherData = ref([
|
||||
},
|
||||
]);
|
||||
|
||||
const dustData = ref({
|
||||
light: '2000Lux', // 光照强度
|
||||
wind: '15m³/h', // 排风量
|
||||
evapotranspiration: '2000Lux', // 蒸腾
|
||||
wet: {
|
||||
min: '26%',
|
||||
max: '34%',
|
||||
},
|
||||
temp: {
|
||||
min: '32℃',
|
||||
max: '28℃',
|
||||
},
|
||||
air: {
|
||||
dirt: '300ppm',
|
||||
wet: '34%',
|
||||
},
|
||||
pressure: {
|
||||
input: '11',
|
||||
end: 2,
|
||||
},
|
||||
flow: {
|
||||
temp: '53',
|
||||
input: '125',
|
||||
},
|
||||
fertilization: {
|
||||
ph: 8,
|
||||
output: '18',
|
||||
},
|
||||
});
|
||||
|
||||
// 定义合法的天气状态流转规则
|
||||
const weatherStates = [
|
||||
{
|
||||
type: 'sunny',
|
||||
next: ['sunny', 'sunnyToCloudy'], // 晴天只能转晴天或晴转多云
|
||||
tempEffect: +0, // 温度影响
|
||||
},
|
||||
{
|
||||
type: 'sunnyToCloudy',
|
||||
next: ['thunderRain', 'rainy'], // 晴转多云后可能转雷雨或直接下雨
|
||||
tempEffect: +1,
|
||||
},
|
||||
{
|
||||
type: 'thunderRain',
|
||||
next: ['rainy', 'windy'], // 雷雨后继续下雨或转大风
|
||||
tempEffect: -2,
|
||||
},
|
||||
{
|
||||
type: 'rainy',
|
||||
next: ['rainy', 'windy'], // 雨天可能持续或转大风
|
||||
tempEffect: -3,
|
||||
},
|
||||
{
|
||||
type: 'windy',
|
||||
next: ['sunny'], // 大风后转晴天
|
||||
tempEffect: -4,
|
||||
},
|
||||
];
|
||||
|
||||
const currentPosition = ref('孟定镇');
|
||||
|
||||
const currentData = ref({
|
||||
temp: 18, // 当前温度
|
||||
PM2: 80, // 空气质量 μg/m³
|
||||
@ -350,11 +412,141 @@ const currentData = ref({
|
||||
windSpeed: 1.5, // 风速 m/s
|
||||
PH: 6.5, // 土壤酸碱度
|
||||
});
|
||||
// 基础环境数据模板
|
||||
|
||||
// #endregion
|
||||
|
||||
/* --------------- methods --------------- */
|
||||
// #region
|
||||
const changeMap = (params) => {
|
||||
currentPosition.value = params.message;
|
||||
const newWeatherData = generateSmartWeather(15, 6, 18);
|
||||
weatherData.value = newWeatherData;
|
||||
currentData.value = generateFarmData(18);
|
||||
};
|
||||
|
||||
const changeMap2 = (params) => {
|
||||
console.log(params.message);
|
||||
dustData.value = Mock.mock({
|
||||
// 光照强度 (500-3000 Lux)
|
||||
light: '@natural(500, 3000)' + 'Lux',
|
||||
|
||||
// 排风量 (5-30 m³/h)
|
||||
wind: '@natural(5, 30)' + 'm³/h',
|
||||
|
||||
// 蒸腾量 (1-10 mm/day)
|
||||
evapotranspiration: '@natural(1, 10)' + 'mm/day',
|
||||
|
||||
// 湿度范围 (min:20-40%, max比min高5-15%)
|
||||
wet: {
|
||||
min: '@natural(20, 40)' + '%',
|
||||
max: function () {
|
||||
return parseInt(this.min) + Mock.mock('@natural(5, 15)') + '%';
|
||||
},
|
||||
},
|
||||
|
||||
// 温度范围 (min:15-25℃, max比min高5-15℃)
|
||||
temp: {
|
||||
min: '@natural(15, 25)' + '℃',
|
||||
max: function () {
|
||||
return parseInt(this.min) + Mock.mock('@natural(5, 15)') + '℃';
|
||||
},
|
||||
},
|
||||
|
||||
// 空气质量
|
||||
air: {
|
||||
// 颗粒物浓度 (100-800 ppm)
|
||||
dirt: '@natural(100, 800)' + 'ppm',
|
||||
wet: '@natural(30, 60)%', // 先独立生成
|
||||
},
|
||||
|
||||
// 压力参数
|
||||
pressure: {
|
||||
// 输入压力 (5-15 MPa)
|
||||
input: '@natural(5, 15)',
|
||||
// 末端压力衰减 (1-5)
|
||||
end: '@natural(1, 5)',
|
||||
},
|
||||
|
||||
// 流量参数
|
||||
flow: {
|
||||
// 流体温度 (30-60 m²/h)
|
||||
temp: '@natural(30, 60)',
|
||||
// 输入流量 (50-200 m²/h)
|
||||
input: '@natural(50, 200)',
|
||||
},
|
||||
|
||||
// 施肥参数
|
||||
fertilization: {
|
||||
// pH值 (5.0-8.5)
|
||||
ph: '@float(5, 8.5, 1, 1)',
|
||||
// 输出流量 (10-50 m²/h)
|
||||
output: '@natural(10, 50)',
|
||||
},
|
||||
});
|
||||
};
|
||||
// 生成智能天气数据
|
||||
function generateSmartWeather(startTime, hours, baseTemp = 18) {
|
||||
let currentWeather = 'sunny';
|
||||
const result = [];
|
||||
|
||||
for (let i = 0; i < hours; i++) {
|
||||
// 1. 获取当前天气状态对象
|
||||
const state = weatherStates.find((s) => s.type === currentWeather);
|
||||
|
||||
// 2. 计算时间 (15:00 格式)
|
||||
const time = `${startTime + i}:00`.padStart(5, '0');
|
||||
|
||||
// 3. 计算温度(基于基础温度+天气影响+随机波动)
|
||||
const temp = baseTemp + state.tempEffect + Math.floor(Math.random() * 2);
|
||||
|
||||
// 4. 记录数据
|
||||
result.push({
|
||||
weather: currentWeather,
|
||||
time,
|
||||
temp: Math.max(temp, -5), // 确保不低于-5度
|
||||
});
|
||||
|
||||
// 5. 智能切换下一个天气状态
|
||||
currentWeather = state.next[Math.floor(Math.random() * state.next.length)];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const generateFarmData = (baseTemp = 18) => {
|
||||
// 1. 先生成核心天气数据(基于之前方案)
|
||||
const weather = Mock.mock({
|
||||
weather: '@pick(["sunny", "sunnyToCloudy", "thunderRain", "rainy", "windy"])',
|
||||
wind: '@pick(["东南风", "西南风", "东北风", "西北风"])',
|
||||
windSpeed: '@float(0.5, 5, 1, 1)',
|
||||
});
|
||||
|
||||
// 2. 根据天气计算衍生数据
|
||||
const weatherImpact = {
|
||||
sunny: { temp: +0, light: +200, wet: -10 },
|
||||
sunnyToCloudy: { temp: +1, light: -100, wet: +5 },
|
||||
thunderRain: { temp: -2, light: -300, wet: +20 },
|
||||
rainy: { temp: -3, light: -400, wet: +25 },
|
||||
windy: { temp: -4, light: +50, wet: -15 },
|
||||
}[weather.weather];
|
||||
|
||||
// 3. 生成完整数据集(带科学关联)
|
||||
return {
|
||||
temp: Mock.Random.float(baseTemp + weatherImpact.temp - 1, baseTemp + weatherImpact.temp + 1, 1, 1),
|
||||
PM2: Mock.Random.integer(weather.weather === 'windy' ? 30 : 60, weather.weather === 'rainy' ? 90 : 120),
|
||||
PM10: Mock.Random.integer(80, 150),
|
||||
bugs: Mock.Random.integer(0, weather.wet > 70 ? 5 : 2), // 高湿度虫害增加
|
||||
dustTemp: Mock.Random.float(baseTemp - 3, baseTemp + 1, 1, 1),
|
||||
wet: Mock.Random.integer(50 + weatherImpact.wet, 70 + weatherImpact.wet),
|
||||
sick: Mock.Random.integer(0, weather.weather === 'thunderRain' ? 3 : 1), // 雷雨易引发病害
|
||||
dustWet: Mock.Random.integer(60, weather.weather === 'rainy' ? 85 : 75),
|
||||
wind: weather.wind,
|
||||
light: Mock.Random.integer(Math.max(200, 500 + weatherImpact.light), Math.min(1200, 800 + weatherImpact.light)),
|
||||
windSpeed: weather.windSpeed,
|
||||
PH: Mock.Random.float(6.0, 7.5, 1, 1),
|
||||
};
|
||||
};
|
||||
// #endregion
|
||||
</script>
|
||||
|
||||
|