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': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
},
|
},
|
||||||
proxy: {
|
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]: {
|
[VITE_APP_BASE_API]: {
|
||||||
target: VITE_APP_BASE_URL,
|
target: VITE_APP_BASE_URL,
|
||||||
changeOrigin: true,
|
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',
|
path: '/sub-operation-service/brand',
|
||||||
name: 'brand',
|
name: 'brand',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/sub-operation-service/brand/index',
|
redirect: '/sub-operation-service/brand/apply', // 默认页
|
||||||
meta: { title: '公共品牌' },
|
meta: { title: '公共品牌' },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/sub-operation-service/brand/index',
|
path: 'apply',
|
||||||
component: () => import('@/views/brand/index.vue'),
|
component: () => import('@/views/brand/index.vue'),
|
||||||
name: 'brandMain',
|
children: [
|
||||||
meta: { title: '公共品牌首页' },
|
{
|
||||||
|
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',
|
path: '/sub-operation-service/ecommerce',
|
||||||
name: 'ecommerce',
|
name: 'ecommerce',
|
||||||
|
@ -50,6 +50,10 @@ publicAxios.interceptors.request.use(async (config) => {
|
|||||||
config.headers['Content-Type'] = config.uploadType;
|
config.headers['Content-Type'] = config.uploadType;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'mock': {
|
||||||
|
config.baseURL = ''; // 不走 VITE_APP_BASE_API,直接请求 mock 路径
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
config.baseURL = VITE_APP_BASE_API;
|
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-col v-for="product in products" :key="product.id" :span="6">
|
||||||
<el-card class="box-card" :body-style="{ padding: '8px', height: '100%' }">
|
<el-card class="box-card" :body-style="{ padding: '8px', height: '100%' }">
|
||||||
<div class="flex-column">
|
<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">
|
<div class="flex-1 flex-around">
|
||||||
<p>{{ product.name }}</p>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
@ -16,32 +16,25 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import productsApi from '@/apis/products';
|
import { getApplyList } from '@/apis/brand';
|
||||||
import { getAssetsFile } from '@/utils/index.js';
|
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 products = ref([]);
|
||||||
|
|
||||||
// 获取商品数据
|
// 获取商品数据
|
||||||
const fetchProducts = async () => {
|
getApplyList().then((res) => {
|
||||||
try {
|
products.value = res.data;
|
||||||
const data = await productsApi.getProducts();
|
});
|
||||||
const processedData = data.map((item) => {
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
img: getAssetsFile(item.imageUrl), // 确保图片路径正确
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
products.value = processedData;
|
function gotoApplication(id) {
|
||||||
} catch (error) {
|
router.push(`/sub-operation-service/brand/apply/${id}`);
|
||||||
console.error('获取商品数据失败:', error);
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 组件挂载时获取数据
|
// 组件挂载时获取数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchProducts();
|
getApplyList;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -95,6 +88,7 @@ onMounted(() => {
|
|||||||
.button {
|
.button {
|
||||||
width: 96px;
|
width: 96px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</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>
|
<template>
|
||||||
<div class="usage-monitor">
|
<div class="usage-monitor">
|
||||||
<!-- 顶部 TabPane,居中显示 -->
|
<!-- 顶部 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="onSale" />
|
||||||
<el-tab-pane label="未上架" name="offShelf" />
|
<el-tab-pane label="未上架" name="offShelf" />
|
||||||
<el-tab-pane label="已失效" name="expired" />
|
<el-tab-pane label="已失效" name="expired" />
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 列表内容 -->
|
<!-- 列表内容 -->
|
||||||
<div class="list-wrapper">
|
<div class="list-wrapper">
|
||||||
@ -19,21 +18,18 @@
|
|||||||
<div class="item-info">
|
<div class="item-info">
|
||||||
<div class="item-name">{{ p.name }}</div>
|
<div class="item-name">{{ p.name }}</div>
|
||||||
<div class="item-stats">月售 {{ p.monthlySales }} · 库存 {{ p.stock }}</div>
|
<div class="item-stats">月售 {{ p.monthlySales }} · 库存 {{ p.stock }}</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 价格 -->
|
|
||||||
<div class="item-price">¥ {{ p.price }} /kg</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>
|
||||||
|
|
||||||
<!-- 右侧状态 -->
|
<div class="item-buttom">
|
||||||
<div class="item-status" :class="statusClass(activeTab)">
|
<div class="item-status" :class="statusClass(activeTab)">
|
||||||
{{ tabLabels[activeTab] }}
|
{{ tabLabels[activeTab] }}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -41,6 +37,8 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
|
import { getAssetsFile } from '@/utils/index.js';
|
||||||
|
import { getMonitorList } from '@/apis/brand';
|
||||||
|
|
||||||
const activeTab = ref('onSale');
|
const activeTab = ref('onSale');
|
||||||
|
|
||||||
@ -55,7 +53,7 @@ const products = ref([
|
|||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: '耿马镇沙疆西红柿',
|
name: '耿马镇沙疆西红柿',
|
||||||
img: 'https://via.placeholder.com/80',
|
img: getAssetsFile('images/brand/product4.png'),
|
||||||
monthlySales: 999,
|
monthlySales: 999,
|
||||||
stock: 10000,
|
stock: 10000,
|
||||||
price: 3.0,
|
price: 3.0,
|
||||||
@ -64,7 +62,7 @@ const products = ref([
|
|||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: '耿马镇沙疆土豆',
|
name: '耿马镇沙疆土豆',
|
||||||
img: 'https://via.placeholder.com/80',
|
img: getAssetsFile('images/brand/product6.png'),
|
||||||
monthlySales: 123,
|
monthlySales: 123,
|
||||||
stock: 5000,
|
stock: 5000,
|
||||||
price: 2.5,
|
price: 2.5,
|
||||||
@ -73,7 +71,7 @@ const products = ref([
|
|||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
name: '彩椒南瓜混合',
|
name: '彩椒南瓜混合',
|
||||||
img: 'https://via.placeholder.com/80',
|
img: getAssetsFile('images/brand/product1.png'),
|
||||||
monthlySales: 456,
|
monthlySales: 456,
|
||||||
stock: 8000,
|
stock: 8000,
|
||||||
price: 4.2,
|
price: 4.2,
|
||||||
@ -105,42 +103,42 @@ const statusClass = (tab) => {
|
|||||||
.usage-monitor {
|
.usage-monitor {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.tabs-wrapper {
|
.tabs-wrapper {
|
||||||
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
align-items: center;
|
||||||
margin-bottom: 24px;
|
background-color: #fff;
|
||||||
|
|
||||||
.centered-tabs {
|
:deep(.el-tabs__item) {
|
||||||
width: 600px; /* 根据实际需要调整宽度 */
|
font-size: 24px;
|
||||||
.el-tabs__header {
|
font-weight: 700;
|
||||||
justify-content: center;
|
// border: 1 solid #f000;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-wrapper {
|
.list-wrapper {
|
||||||
.list-item {
|
.list-item {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: 80px 1fr auto auto 80px;
|
justify-content: start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 16px 0;
|
padding: 20px 0;
|
||||||
&.has-border {
|
|
||||||
border-top: 1px solid #ebeef5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-img {
|
.item-img {
|
||||||
width: 80px;
|
width: 120px;
|
||||||
height: 80px;
|
height: 120px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-info {
|
.item-info {
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
|
// background-color: #909399;
|
||||||
.item-name {
|
.item-name {
|
||||||
font-size: 16px;
|
font-size: 20px;
|
||||||
font-weight: 500;
|
font-weight: 700;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
.item-stats {
|
.item-stats {
|
||||||
@ -150,21 +148,37 @@ const statusClass = (tab) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.item-price {
|
.item-price {
|
||||||
font-size: 18px;
|
font-size: 28px;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
color: #67c23a;
|
color: #67c23a;
|
||||||
text-align: right;
|
text-align: left;
|
||||||
padding: 0 16px;
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-buttom {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 110px;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-actions {
|
.item-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-status {
|
.item-status {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-size: 14px;
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
&.text-success {
|
&.text-success {
|
||||||
color: #67c23a;
|
color: #67c23a;
|
||||||
}
|
}
|
@ -3,50 +3,51 @@
|
|||||||
<el-container class="brand-layout-container">
|
<el-container class="brand-layout-container">
|
||||||
<el-aside class="brand-aside-menu">
|
<el-aside class="brand-aside-menu">
|
||||||
<!-- 菜单部分 -->
|
<!-- 菜单部分 -->
|
||||||
<el-menu v-model:default-active="activeMenu" class="aside-menu" @select="handleMenuSelect">
|
<el-menu :default-active="activeMenu" class="aside-menu" @select="handleSelect">
|
||||||
<el-menu-item index="1">
|
<el-menu-item index="apply">
|
||||||
<el-icon><Document /></el-icon>
|
<img :src="getAssetsFile('images/brand/Apply.png')" class="menu-icon" alt="申请图标" />
|
||||||
<span>使用申请</span>
|
<span>使用申请</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
|
||||||
<el-menu-item index="2">
|
<el-menu-item index="auth">
|
||||||
<el-icon><Lock /></el-icon>
|
<img :src="getAssetsFile('images/brand/autho.png')" class="menu-icon" alt="" />
|
||||||
<span>授权管理</span>
|
<span>授权管理</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
|
||||||
<el-menu-item index="3">
|
<el-menu-item index="monitor">
|
||||||
<el-icon><Monitor /></el-icon>
|
<img :src="getAssetsFile('images/brand/supervision.png')" class="menu-icon" alt="" />
|
||||||
<span>使用监管</span>
|
<span>使用监管</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</el-aside>
|
</el-aside>
|
||||||
|
|
||||||
<el-main class="brand-main">
|
<el-main class="brand-main">
|
||||||
<component :is="currentComponent" />
|
<router-view />
|
||||||
</el-main>
|
</el-main>
|
||||||
</el-container>
|
</el-container>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Document, Lock, Monitor } from '@element-plus/icons-vue';
|
import { computed } from 'vue';
|
||||||
import { ref, shallowRef } from 'vue';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import ApplyManagement from './components/ApplyManagement.vue';
|
import { getAssetsFile } from '@/utils/index.js';
|
||||||
import AuthManagement from './components/AuthManagement.vue';
|
|
||||||
import UsageMonitor from './components/UsageMonitor.vue';
|
|
||||||
// 响应式状态
|
|
||||||
const activeMenu = ref('1');
|
|
||||||
const currentComponent = shallowRef(ApplyManagement);
|
|
||||||
|
|
||||||
|
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) => {
|
function handleSelect(index) {
|
||||||
const components = {
|
router.push(`/sub-operation-service/brand/${index}`);
|
||||||
1: ApplyManagement,
|
}
|
||||||
2: AuthManagement,
|
|
||||||
3: UsageMonitor,
|
|
||||||
};
|
|
||||||
currentComponent.value = components[index];
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -69,6 +70,9 @@ const handleMenuSelect = (index) => {
|
|||||||
line-height: 48px;
|
line-height: 48px;
|
||||||
margin: 4px 8px;
|
margin: 4px 8px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
text-align: left;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #f5f7fa;
|
background-color: #f5f7fa;
|
||||||
}
|
}
|
||||||
@ -78,6 +82,12 @@ const handleMenuSelect = (index) => {
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
.menu-icon {
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
margin-right: 8px;
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
}
|
||||||
span {
|
span {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
@ -11,17 +11,26 @@ import { getAssetsFile } from '@/utils';
|
|||||||
// 您提供的耿马县GeoJSON数据
|
// 您提供的耿马县GeoJSON数据
|
||||||
const gengmaGeoJSON = json;
|
const gengmaGeoJSON = json;
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
const formattedDate = today
|
||||||
|
.toLocaleDateString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
})
|
||||||
|
.replace(/\//g, '/'); // 确保分隔符是 /
|
||||||
|
|
||||||
// 模拟乡镇数据(根据图片中的地名)
|
// 模拟乡镇数据(根据图片中的地名)
|
||||||
const towns = ref([
|
const towns = ref([
|
||||||
{ name: '孟定镇', coord: [99.01, 23.64], weather: '晴', temp: '28℃', icon: 'sunny' },
|
{ id: 0, name: '孟定镇', coord: [99.01, 23.64], weather: '晴', temp: '28℃', icon: 'sunny' },
|
||||||
{ name: '勐简乡', coord: [99.24, 23.79], weather: '多云', temp: '26℃', icon: 'cloudy' },
|
{ id: 1, name: '勐简乡', coord: [99.24, 23.79], weather: '多云', temp: '26℃', icon: 'cloudy' },
|
||||||
{ name: '四排山乡', coord: [99.5, 23.38], weather: '小雨', temp: '24℃', icon: 'rainy' },
|
{ id: 2, name: '四排山乡', coord: [99.5, 23.38], weather: '小雨', temp: '24℃', icon: 'rainy' },
|
||||||
{ name: '大兴乡', coord: [99.8, 23.76], weather: '多云', temp: '25℃', icon: 'cloudy' },
|
{ id: 3, name: '大兴乡', coord: [99.8, 23.76], weather: '多云', temp: '25℃', icon: 'cloudy' },
|
||||||
{ name: '耿马镇', coord: [99.42, 23.66], weather: '多云', temp: '26℃', icon: 'cloudy' },
|
{ id: 4, name: '耿马镇', coord: [99.42, 23.66], weather: '多云', temp: '26℃', icon: 'cloudy' },
|
||||||
{ name: '贺派乡', coord: [99.21, 23.4], weather: '晴', temp: '27℃', icon: 'sunny' },
|
{ id: 5, name: '贺派乡', coord: [99.21, 23.4], weather: '晴', temp: '27℃', icon: 'sunny' },
|
||||||
{ name: '芒洪乡', coord: [99.73, 23.59], weather: '阴', temp: '23℃', icon: 'overcast' },
|
{ id: 6, name: '芒洪乡', coord: [99.73, 23.59], weather: '阴', temp: '23℃', icon: 'overcast' },
|
||||||
{ name: '勐永镇', coord: [99.53, 23.99], weather: '小雨', temp: '22℃', icon: 'rainy' },
|
{ id: 7, name: '勐永镇', coord: [99.53, 23.99], weather: '小雨', temp: '22℃', icon: 'rainy' },
|
||||||
{ name: '勐撒镇', coord: [99.47, 23.85], weather: '晴', temp: '28℃', icon: 'sunny' },
|
{ id: 8, name: '勐撒镇', coord: [99.47, 23.85], weather: '晴', temp: '28℃', icon: 'sunny' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const mapContainer = ref(null);
|
const mapContainer = ref(null);
|
||||||
@ -40,6 +49,11 @@ function getWeatherIconPath(iconType) {
|
|||||||
let path = iconMap[iconType] ? iconMap[iconType] : 'images/smartFarm/sunny.png';
|
let path = iconMap[iconType] ? iconMap[iconType] : 'images/smartFarm/sunny.png';
|
||||||
return getAssetsFile(path);
|
return getAssetsFile(path);
|
||||||
}
|
}
|
||||||
|
const emit = defineEmits(['changeMap']);
|
||||||
|
// 显示天气详情弹窗
|
||||||
|
const showWeatherDetail = (data) => {
|
||||||
|
emit('changeMap', { message: data });
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 添加防御性检查
|
// 添加防御性检查
|
||||||
@ -53,19 +67,13 @@ onMounted(() => {
|
|||||||
chart.on('click', (params) => {
|
chart.on('click', (params) => {
|
||||||
if (params.componentType === 'series') {
|
if (params.componentType === 'series') {
|
||||||
// 点击乡镇标记点
|
// 点击乡镇标记点
|
||||||
console.log('点击乡镇:', params.name);
|
showWeatherDetail(params.name);
|
||||||
showWeatherDetail(params.data);
|
|
||||||
} else if (params.componentType === 'geo') {
|
} else if (params.componentType === 'geo') {
|
||||||
// 点击地图区域
|
// 点击地图区域
|
||||||
console.log('点击地图区域:', params.name);
|
showWeatherDetail(params.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 显示天气详情弹窗
|
|
||||||
const showWeatherDetail = (data) => {
|
|
||||||
console.log(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 注册地图
|
// 注册地图
|
||||||
echarts.registerMap('耿马县', gengmaGeoJSON);
|
echarts.registerMap('耿马县', gengmaGeoJSON);
|
||||||
|
|
||||||
@ -106,6 +114,7 @@ onMounted(() => {
|
|||||||
coordinateSystem: 'geo',
|
coordinateSystem: 'geo',
|
||||||
symbolSize: 30,
|
symbolSize: 30,
|
||||||
data: towns.value.map((town) => ({
|
data: towns.value.map((town) => ({
|
||||||
|
id: town.id,
|
||||||
name: town.name,
|
name: town.name,
|
||||||
value: [...town.coord, town.temp],
|
value: [...town.coord, town.temp],
|
||||||
weather: town.weather,
|
weather: town.weather,
|
||||||
@ -165,9 +174,9 @@ onMounted(() => {
|
|||||||
graphic: {
|
graphic: {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
left: 20,
|
left: 20,
|
||||||
bottom: 5,
|
bottom: 0,
|
||||||
style: {
|
style: {
|
||||||
text: '数据更新于: 2025.01.01 08:00:00',
|
text: '数据更新于: ' + formattedDate,
|
||||||
fill: '#666',
|
fill: '#666',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
},
|
},
|
||||||
|
@ -26,10 +26,12 @@ const towns = ref([
|
|||||||
|
|
||||||
const mapContainer = ref(null);
|
const mapContainer = ref(null);
|
||||||
|
|
||||||
// 获取天气图标路径
|
const emit = defineEmits(['changeMap2']);
|
||||||
// function getWeatherIconPath(iconType) {
|
// 显示天气详情弹窗
|
||||||
// return `/images/${iconType}.png`; // 使用绝对路径
|
const showDustDetail = (data) => {
|
||||||
// }
|
emit('changeMap2', { message: data });
|
||||||
|
};
|
||||||
|
|
||||||
function getWeatherIconPath(iconType) {
|
function getWeatherIconPath(iconType) {
|
||||||
const iconMap = {
|
const iconMap = {
|
||||||
sunny: 'images/smartFarm/sunny.png',
|
sunny: 'images/smartFarm/sunny.png',
|
||||||
@ -47,20 +49,12 @@ onMounted(() => {
|
|||||||
// 添加点击事件监听
|
// 添加点击事件监听
|
||||||
chart.on('click', (params) => {
|
chart.on('click', (params) => {
|
||||||
if (params.componentType === 'series') {
|
if (params.componentType === 'series') {
|
||||||
// 点击乡镇标记点
|
showDustDetail(params.name);
|
||||||
console.log('点击乡镇:', params.name);
|
|
||||||
showWeatherDetail(params.data);
|
|
||||||
} else if (params.componentType === 'geo') {
|
} else if (params.componentType === 'geo') {
|
||||||
// 点击地图区域
|
showDustDetail(params.name);
|
||||||
console.log('点击地图区域:', params.name);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 显示天气详情弹窗
|
|
||||||
const showWeatherDetail = (data) => {
|
|
||||||
console.log(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 注册地图
|
// 注册地图
|
||||||
echarts.registerMap('耿马县', gengmaGeoJSON);
|
echarts.registerMap('耿马县', gengmaGeoJSON);
|
||||||
|
|
||||||
|
@ -6,12 +6,13 @@
|
|||||||
<el-card shadow="hover" style="border-radius: 16px">
|
<el-card shadow="hover" style="border-radius: 16px">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10">
|
<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>
|
||||||
<el-col :span="1"> </el-col>
|
<el-col :span="1"> </el-col>
|
||||||
<el-col :span="13">
|
<el-col :span="13">
|
||||||
<div class="location">
|
<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>
|
||||||
<div style="display: flex; justify-content: space-around">
|
<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">
|
<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="" />
|
<img :src="getAssetsFile('images/smartFarm/光照传感器.png')" alt="" />
|
||||||
光照
|
光照
|
||||||
</div>
|
</div>
|
||||||
<div class="values">2000Lux</div>
|
<div class="values">{{ dustData.light }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dustData">
|
<div class="dustData">
|
||||||
<div>
|
<div>
|
||||||
<img :src="getAssetsFile('images/smartFarm/排风.png')" alt="" />
|
<img :src="getAssetsFile('images/smartFarm/排风.png')" alt="" />
|
||||||
排风
|
排风
|
||||||
</div>
|
</div>
|
||||||
<div class="values">15m³/h</div>
|
<div class="values">{{ dustData.wind }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dustData">
|
<div class="dustData">
|
||||||
<div>
|
<div>
|
||||||
<img :src="getAssetsFile('images/smartFarm/蒸腾.png')" alt="" />
|
<img :src="getAssetsFile('images/smartFarm/蒸腾.png')" alt="" />
|
||||||
蒸腾
|
蒸腾
|
||||||
</div>
|
</div>
|
||||||
<div class="values">2000Lux</div>
|
<div class="values">{{ dustData.evapotranspiration }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex">
|
<div style="display: flex">
|
||||||
@ -122,24 +123,24 @@
|
|||||||
<img :src="getAssetsFile('images/smartFarm/土壤湿度.png')" alt="" />
|
<img :src="getAssetsFile('images/smartFarm/土壤湿度.png')" alt="" />
|
||||||
湿度
|
湿度
|
||||||
</div>
|
</div>
|
||||||
<div class="values">26%</div>
|
<div class="values">{{ dustData.wet.min }}</div>
|
||||||
<div class="values">34%</div>
|
<div class="values">{{ dustData.wet.max }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dustData">
|
<div class="dustData">
|
||||||
<div>
|
<div>
|
||||||
<img :src="getAssetsFile('images/smartFarm/土壤温度.png')" alt="" />
|
<img :src="getAssetsFile('images/smartFarm/土壤温度.png')" alt="" />
|
||||||
温度
|
温度
|
||||||
</div>
|
</div>
|
||||||
<div class="values">32℃</div>
|
<div class="values">{{ dustData.temp.min }}</div>
|
||||||
<div class="values">28℃</div>
|
<div class="values">{{ dustData.temp.max }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dustData">
|
<div class="dustData">
|
||||||
<div>
|
<div>
|
||||||
<img :src="getAssetsFile('images/smartFarm/空气.png')" alt="" />
|
<img :src="getAssetsFile('images/smartFarm/空气.png')" alt="" />
|
||||||
空气
|
空气
|
||||||
</div>
|
</div>
|
||||||
<div class="values">300ppm</div>
|
<div class="values">{{ dustData.air.dirt }}</div>
|
||||||
<div class="values">34%</div>
|
<div class="values">{{ dustData.air.wet }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="report">
|
<div class="report">
|
||||||
@ -155,7 +156,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="width: 35%">
|
<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>
|
||||||
<div style="width: 35%">
|
<div style="width: 35%">
|
||||||
<div style="margin-top: 70px; display: flex; text-align: left">
|
<div style="margin-top: 70px; display: flex; text-align: left">
|
||||||
@ -164,11 +165,11 @@
|
|||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<span style="font-size: 15px">输入</span>
|
<span style="font-size: 15px">输入</span>
|
||||||
<span class="values">11</span>
|
<span class="values">{{ dustData.pressure.input }}</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<span style="font-size: 15px">末端</span>
|
<span style="font-size: 15px">末端</span>
|
||||||
<span class="values">2</span>
|
<span class="values">{{ dustData.pressure.end }}</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -177,11 +178,11 @@
|
|||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<span style="font-size: 15px">灌溉</span>
|
<span style="font-size: 15px">灌溉</span>
|
||||||
<span class="values">18℃</span>
|
<span class="values">{{ dustData.flow.output }}</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<span style="font-size: 15px">回液</span>
|
<span style="font-size: 15px">回液</span>
|
||||||
<span class="values">18℃</span>
|
<span class="values">{{ dustData.flow.input }}</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -192,11 +193,11 @@
|
|||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<span style="font-size: 15px">PH</span>
|
<span style="font-size: 15px">PH</span>
|
||||||
<span class="values">8</span>
|
<span class="values">{{ dustData.fertilization.ph }}</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<span style="font-size: 15px">输入</span>
|
<span style="font-size: 15px">输入</span>
|
||||||
<span class="values">18℃</span>
|
<span class="values">{{ dustData.fertilization.output }}</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -293,13 +294,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref } from 'vue';
|
||||||
import Common from './components/common.vue';
|
import Common from './components/common.vue';
|
||||||
import * as echarts from 'echarts';
|
|
||||||
import MapComp from '@/views/smartFarm/components/mapComp.vue';
|
import MapComp from '@/views/smartFarm/components/mapComp.vue';
|
||||||
import { getAssetsFile } from '@/utils/index.js';
|
import { getAssetsFile } from '@/utils/index.js';
|
||||||
import MapSimple from '@/views/smartFarm/components/mapSimple.vue';
|
import MapSimple from '@/views/smartFarm/components/mapSimple.vue';
|
||||||
import ChartsFlow from '@/views/smartFarm/components/charts-flow.vue';
|
import ChartsFlow from '@/views/smartFarm/components/charts-flow.vue';
|
||||||
|
import Mock from 'mockjs';
|
||||||
|
|
||||||
/* --------------- data --------------- */
|
/* --------------- data --------------- */
|
||||||
// #region
|
// #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({
|
const currentData = ref({
|
||||||
temp: 18, // 当前温度
|
temp: 18, // 当前温度
|
||||||
PM2: 80, // 空气质量 μg/m³
|
PM2: 80, // 空气质量 μg/m³
|
||||||
@ -350,11 +412,141 @@ const currentData = ref({
|
|||||||
windSpeed: 1.5, // 风速 m/s
|
windSpeed: 1.5, // 风速 m/s
|
||||||
PH: 6.5, // 土壤酸碱度
|
PH: 6.5, // 土壤酸碱度
|
||||||
});
|
});
|
||||||
|
// 基础环境数据模板
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
/* --------------- methods --------------- */
|
/* --------------- methods --------------- */
|
||||||
// #region
|
// #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
|
// #endregion
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|