This commit is contained in:
2090205686@qq.com 2025-05-21 14:33:39 +08:00
commit 05d99fb7e0
20 changed files with 1082 additions and 369 deletions

View File

@ -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,

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

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

View File

@ -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',

View File

@ -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;
} }

View File

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

View File

@ -3,10 +3,10 @@
<el-col v-for="product in products" :key="product.id" :span="6"> <el-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>

View File

@ -0,0 +1,390 @@
<template>
<div class="auth-management">
<!-- 统计卡片 -->
<el-row :gutter="20">
<el-col :span="12">
<el-card shadow="hover" style="border-radius: 24px" :body-class="'card-body'">
<div class="card-left">
<img :src="getAssetsFile('images/brand/1532.png')" alt="" />
<div class="card-content flex-1 flex-column">
<div class="stat-number">999 <span></span></div>
<div class="stat-label">授权产品</div>
</div>
</div>
<div class="card-right">
<img :src="getAssetsFile('images/brand/cardLeft.png')" alt="" />
<p>较上月上涨 <span>7</span> </p>
</div>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="hover" style="border-radius: 24px" :body-class="'card-body'">
<div class="card-left">
<img :src="getAssetsFile('images/brand/1533.png')" alt="" />
<div class="card-content flex-1 flex-column">
<div class="stat-number">999 <span></span></div>
<div class="stat-label">授权产品</div>
</div>
</div>
<div class="card-right">
<img :src="getAssetsFile('images/brand/cardRight.png')" alt="" />
<p>较上月上涨 <span>7</span> </p>
</div>
</el-card>
</el-col>
</el-row>
<!-- 产品列表 -->
<el-card shadow="hover" style="border-radius: 16px" class="product-card">
<!-- 状态筛选 -->
<el-tabs v-model="activeStatus" class="tabs-wrapper">
<el-tab-pane label="已授权" name="authorized" />
<el-tab-pane label="审批中" name="approving" />
<el-tab-pane label="已失效" name="expired" />
</el-tabs>
<div class="product-list">
<div v-for="(product, index) in filteredProducts" :key="product.id" class="product-item" :class="{ 'border-top': index > 0 }">
<div class="product-info">
<img class="product-img" :src="product.img" alt="product" />
<div class="product-text">
<span class="product-name">{{ product.name }}</span>
<div class="detail-item">
<label>检测批次</label>
<span>{{ product.batch }}</span>
</div>
<div class="detail-item">
<label>授权期限</label>
<span>{{ product.duration }}</span>
</div>
<div class="detail-item">
<label>到期时间</label>
<span class="text-expire">{{ product.expireDate }}</span>
</div>
</div>
</div>
<div class="product-action">
<el-tag :class="statusClass(activeTab)" :type="statusTypeMap[product.status]">{{ product.statusLabel }}</el-tag>
<el-button
v-if="product.status === 'authorized'"
type="primary"
plain
:icon="Edit"
size="large"
style="border-radius: 8px"
@click="handleCertificate(product)"
>
授权证书
</el-button>
</div>
</div>
</div>
</el-card>
<!-- 证书预览 -->
<el-dialog v-model="certificateDialogVisible" title="授权证书" width="30%" destroy-on-close>
<div style="display: flex; justify-content: center">
<el-image
:src="currentCertificateImg"
fit="contain"
style="max-width: 600px; width: 80%"
:preview-src-list="[currentCertificateImg]"
:z-index="9999"
/>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { getAssetsFile } from '@/utils/index.js';
import { getProducts } from '@/apis/brand';
import { Edit } from '@element-plus/icons-vue';
const activeStatus = ref('authorized');
const statusTypeMap = {
authorized: 'success',
approving: 'warning',
expired: 'danger',
};
const products = ref([
{
id: 1,
name: '耿马镇沙疆西红柿',
batch: '10021',
duration: '6个月',
expireDate: '2025.01.01',
status: 'authorized',
statusLabel: '已授权',
img: getAssetsFile('images/brand/product4.png'),
},
{
id: 2,
name: '孟弄乡沙地土豆',
batch: '10022',
duration: '6个月',
expireDate: '2025.01.02',
status: 'authorized',
statusLabel: '已授权',
img: getAssetsFile('images/brand/product6.png'),
},
{
id: 3,
name: '芒洪乡水果彩椒',
batch: '10021',
duration: '6个月',
expireDate: '2025.01.01',
status: 'authorized',
statusLabel: '已授权',
img: getAssetsFile('images/brand/product2.png'),
},
{
id: 4,
name: '耿马镇沙疆西红柿',
batch: '10021',
duration: '6个月',
expireDate: '2025.01.01',
status: 'approving',
statusLabel: '审批中',
img: getAssetsFile('images/brand/product4.png'),
},
{
id: 5,
name: '孟弄乡沙地土豆',
batch: '10022',
duration: '6个月',
expireDate: '2025.01.02',
status: 'approving',
statusLabel: '审批中',
img: getAssetsFile('images/brand/product6.png'),
},
{
id: 6,
name: '芒洪乡水果彩椒',
batch: '10021',
duration: '6个月',
expireDate: '2025.01.01',
status: 'expired',
statusLabel: '已失效',
img: getAssetsFile('images/brand/product2.png'),
},
]);
const statusClass = (tab) => {
if (tab === 'approving') return 'text-warning status-tag';
if (tab === 'expired') return 'text-danger status-tag';
return 'text-success status-tag';
};
const filteredProducts = computed(() => products.value.filter((p) => p.status === activeStatus.value));
const certificateDialogVisible = ref(false);
const currentCertificateImg = ref('');
const handleCertificate = (product) => {
console.log('查看证书:', product);
//
currentCertificateImg.value = getAssetsFile('images/brand/sqzs.png'); // product.certificate
certificateDialogVisible.value = true;
};
</script>
<style lang="scss" scoped>
.auth-management {
padding: 0 20px 0 0;
height: 100%;
display: flex;
flex-direction: column;
}
.product-card {
flex: 1;
margin-top: 20px;
}
:deep(.el-card__body) {
height: 100%;
}
.product-list {
height: 100%;
overflow: auto;
}
.product-list::-webkit-scrollbar {
display: none;
}
:deep(.card-body) {
display: flex;
align-items: center;
padding: 0;
width: 100%;
height: 100%;
justify-content: space-between;
}
.card-left:deep(img) {
width: 88px;
display: block;
aspect-ratio: 1/1;
padding: 20px;
margin: 0;
}
.card-right:deep(img) {
width: 133px;
height: 80px;
display: block;
// padding: 20px;
margin: 0;
padding: 8px;
}
.card-left {
width: 50%;
display: flex;
justify-content: space-between;
align-items: center;
.card-content {
height: 88px;
// background-color: #fafafa;
span {
font-size: 16px;
font-weight: 400;
text-align: left;
color: #000000;
}
}
.stat-number {
font-size: 32px;
font-weight: 600;
font-weight: 700;
text-align: left;
color: #000000;
}
.stat-label {
display: flex;
align-items: center;
font-size: 20px;
font-weight: 400;
text-align: left;
color: #999999;
}
}
.card-right {
display: flex;
flex-direction: column;
align-items: center;
width: 50%;
p {
margin: 0;
font-size: 16px;
font-weight: 400;
text-align: left;
color: #000000;
span {
font-size: 16px;
font-weight: 700;
text-align: left;
color: #25bf82;
}
}
}
.tabs-wrapper {
width: 100%;
display: flex;
align-items: center;
background-color: #fff;
:deep(.el-tabs__item) {
font-size: 24px;
font-weight: 700;
border: 1 solid #f000;
}
}
.flex {
display: flex;
flex-direction: row;
}
.flex-column {
display: flex;
flex-direction: column;
}
.flex-1 {
flex: 1;
}
.flex-space-between {
justify-content: space-between;
}
.product-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
.product-info {
display: flex;
flex: 1;
.product-img {
width: 110px;
aspect-ratio: 1 / 1;
object-fit: cover;
padding: 8px;
}
.product-text {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.product-name {
font-size: 20px;
font-weight: 700;
text-align: left;
color: #000000;
}
.detail-item {
width: 100%;
font-size: 16px;
display: flex;
justify-content: space-between;
font-weight: 400;
text-align: left;
color: #999999;
span {
color: #000000;
}
}
}
}
.product-action {
width: 200px;
height: 110px;
padding: 8px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: end;
.status-tag {
text-align: right;
font-size: 20px;
font-weight: 700;
.text-success {
color: #67c23a;
}
.text-warning {
color: #e6a23c;
}
.text-danger {
color: #f56c6c;
}
}
}
}
</style>

View File

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

View File

@ -1,13 +1,12 @@
<template> <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,20 +18,17 @@
<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 class="item-price">¥ {{ p.price }} /kg</div>
</div> </div>
<!-- 价格 --> <div class="item-buttom">
<div class="item-price">¥ {{ p.price }} /kg</div> <div class="item-status" :class="statusClass(activeTab)">
{{ tabLabels[activeTab] }}
<!-- 操作按钮 --> </div>
<div class="item-actions"> <div class="item-actions">
<el-button size="small" @click="onInspect(p)">抽查</el-button> <el-button size="large" class="button" @click="onInspect(p)">抽查</el-button>
<el-button size="small" type="danger" @click="onRevoke(p)">取消授权</el-button> <el-button size="large" class="button" type="danger" @click="onRevoke(p)">取消授权</el-button>
</div> </div>
<!-- 右侧状态 -->
<div class="item-status" :class="statusClass(activeTab)">
{{ tabLabels[activeTab] }}
</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;
} }

View File

@ -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;
} }

View File

@ -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,
}, },

View File

@ -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);

View File

@ -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">&nbsp;</el-col> <el-col :span="1">&nbsp;</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">15/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%, maxmin5-15%)
wet: {
min: '@natural(20, 40)' + '%',
max: function () {
return parseInt(this.min) + Mock.mock('@natural(5, 15)') + '%';
},
},
// (min:15-25, maxmin5-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>