提交 1f882180 作者: 方治民

Merge branches 'dev' and 'dev' of https://gitlab.yiring.com/digital-agri/agri-app into dev

......@@ -112,15 +112,15 @@ export default {
if (this.map && this.map.remove) {
this.map.remove()
}
let style = merge(defaultStyle, options?.style);
let style = merge({}, defaultStyle, options?.style);
//是否卫星图显示
if (options?.style.isImg) {
style.layers.push({
style.layers = [...style.layers, {
id: 'wms-img_w-layer',
type: 'raster',
source: 'wms-img_w-source',
layout: { visibility: 'visible' },
})
}]
}
// [107.570282, 19.474339],
// [115.629717, 34.466859],
......
......@@ -10,6 +10,10 @@ export interface BasicMapPage {
*/
init: boolean
/**
* 地图渲染是否完成初始化(用于区分页面数据初始化和地图 geojson 渲染初始化)
*/
mapInit?: boolean
/**
* 页面正在请求的数据源数量,由于 setGeoJSONSourceForRequest 的异步特性,需要记录当前正在请求的数据源数量,当所有数据源请求完成时,隐藏 Loading
*/
requests?: number
......
......@@ -113,10 +113,11 @@
function submit() {
formRef.value.validator(pageData.form, pageData.rules, true).then((res) => {
if (res.isPassed) {
pageData.loading = true
NongchangAPI.saveFarmbase(pageData.form).then(() => {
toastRef.value.show({ type: 'success', text: '保存成功' })
setTimeout(() => uni.navigateBack(), 1500)
})
}).finally(() => pageData.loading = false)
}
})
}
......@@ -124,20 +125,48 @@
<template>
<view class="page">
<view class="container">
<view class="form-banner">
<text class="title">{{ isSave ? '新增基地' : '基地详情' }}</text>
<text class="subtitle">{{ isSave ? '录入生产基地信息,完善经营档案' : '查看基地详细信息' }}</text>
</view>
<view class="form-content">
<fui-form ref="formRef">
<view class="section-card">
<fui-input label="基地名称" placeholder="请输入" v-model="form.baseName" required />
<fui-input disabled required label="所在地区" v-model="form.address" @click="show.address = true" />
<fui-input label="详细地址" placeholder="请输入" v-model="form.addressDetail" required />
<view class="card-header">
<view class="line"></view>
<text>基地基础信息</text>
</view>
<fui-input label="基地名称" placeholder="请输入基地名称" v-model="form.baseName" required label-size="28" />
<fui-form-item required asterisk label="所在地区" labelSize="28" :labelWeight="400" labelWidth="auto">
<view class="picker-input" @click="show.address = true">
<text class="select-text" :class="{ placeholder: !form.address }">
{{ form.address || '请选择所在地区' }}
</text>
<fui-icon name="arrowright" :size="32" color="#ccc"></fui-icon>
</view>
</fui-form-item>
<fui-input label="详细地址" placeholder="请输入详细地址" v-model="form.addressDetail" required label-size="28" />
</view>
<view class="section-card">
<fui-input type="number" label="基地规模" v-model="form.scale" required>
<view class="card-header">
<view class="line"></view>
<text>经营规模信息</text>
</view>
<fui-input type="number" label="基地规模" v-model="form.scale" required label-size="28">
<template #suffix><text class="unit"></text></template>
</fui-input>
<fui-input disabled required label="种植作物" v-model="form.growCropsText" @click="show.growCrops = true" />
<fui-form-item required asterisk label="种植作物" labelSize="28" :labelWeight="400" labelWidth="auto">
<view class="picker-input" @click="show.growCrops = true">
<text class="select-text" :class="{ placeholder: !form.growCropsText }">
{{ form.growCropsText || '请选择种植作物' }}
</text>
<fui-icon name="arrowright" :size="32" color="#ccc"></fui-icon>
</view>
</fui-form-item>
</view>
<!-- 更多字段... -->
<view class="submit-area" v-if="isSave">
<fui-button text="确认保存" radius="100rpx" @click="submit" />
</view>
......@@ -147,14 +176,142 @@
<AreaPicker v-model:show="show.address" :layer="3" title="选择所属地区" @confirm="handleAreaConfirm" />
<fui-picker :show="show.growCrops" :options="options.growCrops" @change="handleGrowCropsChange" @cancel="show.growCrops = false" />
<fui-toast ref="toastRef" />
<fui-loading isFixed v-if="pageData.loading" backgroundColor="rgba(0, 0, 0, 0.4)" />
</view>
</template>
<style lang="scss" scoped>
.page { background-color: #f7f8fa; min-height: 100vh; font-family: 'DingTalk Sans', sans-serif; }
.container { padding: 24rpx; }
.section-card { background: #fff; border-radius: 24rpx; padding: 10rpx 24rpx; margin-bottom: 24rpx; }
.submit-area { padding: 40rpx 20rpx; }
.unit { font-size: 26rpx; color: #999; margin-left: 12rpx; }
:deep(.fui-input__label) { font-family: 'DingTalk Sans' !important; }
</style>
.page {
min-height: 100vh;
background-color: #f4f7f5;
font-family: 'DingTalk Sans', sans-serif;
}
.form-banner {
background-color: #5db66f;
padding: 60rpx 40rpx 100rpx;
color: #fff;
.title {
font-size: 48rpx;
font-weight: bold;
display: block;
letter-spacing: 2rpx;
}
.subtitle {
font-size: 26rpx;
opacity: 0.9;
margin-top: 12rpx;
display: block;
}
}
.form-content {
padding: 24rpx;
margin-top: -80rpx;
position: relative;
z-index: 10;
}
.section-card {
background-color: #fff;
border-radius: 28rpx;
padding: 32rpx;
margin-bottom: 28rpx;
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.03);
.card-header {
margin-bottom: 32rpx;
display: flex;
align-items: center;
.line {
width: 8rpx;
height: 32rpx;
background-color: #5db66f;
border-radius: 4rpx;
margin-right: 16rpx;
}
text {
font-size: 32rpx;
font-weight: bold;
color: #1a1a1a;
}
}
}
.picker-input {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
width: 100%;
}
.select-text {
font-size: 28rpx;
color: #333333;
flex: 1;
&.placeholder {
color: #999999;
}
}
.unit {
font-size: 26rpx;
color: #999;
margin-left: 12rpx;
}
.submit-area {
padding: 60rpx 20rpx 100rpx;
:deep(.fui-button) {
background: #5db66f !important;
border-color: #5db66f !important;
box-shadow: 0 8rpx 24rpx rgba(93, 182, 111, 0.3);
}
}
:deep(.fui-form) {
background: transparent;
}
:deep(.fui-form__item) {
background: transparent;
border: none;
margin-bottom: 0;
padding: 0;
}
:deep(.fui-input__wrap) {
padding: 28rpx 0;
}
:deep(.fui-input__label) {
font-size: 28rpx;
color: #333333;
font-weight: 400;
font-family: 'DingTalk Sans', system-ui !important;
}
:deep(.fui-input__self) {
font-size: 28rpx;
color: #333333;
font-family: 'DingTalk Sans', system-ui !important;
}
:deep(.fui-input__placeholder) {
color: #d0d0d0;
font-size: 28rpx;
font-family: 'DingTalk Sans' !important;
}
::v-deep .uni-input-placeholder {
font-size: 28rpx !important;
color: #999999 !important;
font-family: 'DingTalk Sans' !important;
}
:deep(.fui-button) {
font-family: 'DingTalk Sans' !important;
font-weight: bold !important;
}
</style>
\ No newline at end of file
......@@ -16,13 +16,11 @@ onLoad((pageOptions) => {
})
pageData.form.id = pageOptions.id;
NongchangAPI.farmsGet({ id: pageOptions.id }).then((res) => {
// 将 res 中的数据赋值到 formData 中,key 保持一致
Object.keys(res).forEach((key) => {
if (key in pageData.form) {
pageData.form[key] = res[key]
}
})
// 回显省市区名称
if (res.provinceName && res.cityName && res.districtName) {
pageData.form.provinceName = res.provinceName
pageData.form.cityName = res.cityName
......@@ -30,14 +28,11 @@ onLoad((pageOptions) => {
pageData.form.provinceText = res.provinceName + '/' + res.cityName + '/' + res.districtName
}
// 编辑模式下,如果已有示例图片,需要设置到上传组件中
if (res.coverImage) {
nextTick(() => {
// 设置上传组件的初始文件
const uploadComponent = uploadRef.value
if (uploadComponent) {
uploadComponent.clearFiles()
// 构造文件对象格式,适配uni-file-picker的数据结构
const fileItem = {
url: res.coverImage,
name: res.coverImage.split('/').pop() || 'image.jpg',
......@@ -48,8 +43,6 @@ onLoad((pageOptions) => {
})
}
// 编辑模式下,根据字典值获取对应的文本
// 延迟执行,确保字典数据已加载完成
nextTick(() => {
if (res.farmType) {
const farmTypeItem = pageData.options.farmType.find(item => item.value == res.farmType)
......@@ -69,9 +62,11 @@ onLoad((pageOptions) => {
}
})
onShow(() => {
// 数据字典赋值
initDict()
pageData.form.userId = userStore.getUserInfo.id
if (!pageData.form.id) {
pageData.form.ownerPhone = userStore.getUserInfo?.phone || ''
}
})
const pageData = reactive({
......@@ -165,6 +160,8 @@ const pageData = reactive({
],
})
const isEditMode = computed(() => !!pageData.form.id)
function initDict() {
pageData.options.mainProducts = dictStore.getDictList.main_business.map((item) => {
return {
......@@ -197,11 +194,10 @@ function handleChangeGrowCrops(e) {
}
const toastRef = ref()
const uploadRef = ref()
// 文件上传
function handleUpload(file) {
pageData.loading = true
uni.uploadFile({
url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`, // 直接使用上传接口URL
url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`,
filePath: file.tempFiles[0].path,
name: 'file',
formData: {
......@@ -218,7 +214,7 @@ function handleUpload(file) {
type: 'success',
text: '上传成功',
})
pageData.form.coverImage = data.message // 保存返回的图片信息
pageData.form.coverImage = data.message
}
}
},
......@@ -235,7 +231,6 @@ function handleUpload(file) {
},
})
}
// 文件删除
function handleDelete(file) {
uploadRef.value.clearFiles()
pageData.form.coverImage = null
......@@ -246,7 +241,6 @@ function submit() {
if (res.isPassed) {
pageData.loading = true
// 提交前处理:通过对provinceText值按/分隔,分别赋值给provinceName、cityName和districtName
if (pageData.form.provinceText) {
const addressParts = pageData.form.provinceText.split('/')
if (addressParts.length >= 3) {
......@@ -264,22 +258,14 @@ function submit() {
text: pageData.form.id ? '保存修改成功' : '添加农场成功',
})
// 获取农场ID:编辑时使用当前ID,新增时从返回值获取(假设返回的是ID或包含ID的对象)
// 根据后端接口惯例,如果返回的是ID字符串直接使用,如果是对象则取id字段
// 暂时假设res为ID或者不影响跳转,优先使用pageData.form.id,如果是新增则尝试从res获取
// 如果res不是id,则需要确认接口返回值结构。通常新增接口返回ID比较合理。
// 鉴于不确定接口返回值,这里先假设编辑时id已知,新增时尝试从res获取,如果获取不到则回退到列表页
const targetId = pageData.form.id || (typeof res === 'string' ? res : res?.id);
setTimeout(() => {
if (targetId) {
// 跳转到农场详情页,注意路径需要是绝对路径
uni.redirectTo({
url: `/pages/nongchang/detail/index?id=${targetId}`
})
} else {
// 如果没有ID,回退上一页
uni.navigateBack()
}
}, 500);
......@@ -294,10 +280,18 @@ function submit() {
<template>
<view class="page">
<view class="page-container">
<view class="form-banner">
<text class="title">{{ isEditMode ? '编辑农场' : '添加农场' }}</text>
<text class="subtitle">{{ isEditMode ? '修改农场信息,完善经营资料' : '创建您的农场档案,开启智慧管理' }}</text>
</view>
<view class="form-content">
<fui-form ref="formRef">
<!-- 第一组表单 -->
<view class="form-card">
<view class="section-card">
<view class="card-header">
<view class="line"></view>
<text>农场基本信息</text>
</view>
<fui-input
required
label="农场名称"
......@@ -316,24 +310,13 @@ function submit() {
:labelWeight="400"
labelWidth="auto"
>
<view class="time-input" @click="pageData.show.farmType = true">
<view class="picker-input" @click="pageData.show.farmType = true">
<text class="select-text" :class="{ placeholder: !pageData.form.farmTypeText }">
{{ pageData.form.farmTypeText || '请选择农场类型' }}
</text>
<fui-icon name="arrowright" :size="32" color="#ccc"></fui-icon>
</view>
</fui-form-item>
<!-- <view class="fui-input-wrapper" @click="pageData.show.farmType = true">
<fui-input
required
readonly
label="农场类型"
placeholder="请选择农场类型"
v-model="pageData.form.farmTypeText"
labelSize="28"
:labelWeight="400"
labelWidth="auto"
/>
</view> -->
<fui-input
required
labelSize="28"
......@@ -354,8 +337,9 @@ function submit() {
label="农场主电话"
placeholder="请输入农场主电话"
v-model="pageData.form.ownerPhone"
readonly
disabled
/>
<!-- 农场简介换行显示 -->
<view class="form-item-block">
<view class="form-item-label required">农场简介</view>
<fui-textarea
......@@ -366,9 +350,11 @@ function submit() {
placeholder="请输入农场简介"
v-model="pageData.form.farmProfiles"
class="block-textarea"
background="#f9f9f9"
radius="16"
:padding="['24rpx','24rpx']"
/>
</view>
<!-- 农场标语 -->
<view class="form-item-block">
<view class="form-item-label">农场标语</view>
<fui-textarea
......@@ -379,12 +365,18 @@ function submit() {
placeholder="请输入农场标语"
v-model="pageData.form.slogan"
class="block-textarea"
background="#f9f9f9"
radius="16"
:padding="['24rpx','24rpx']"
/>
</view>
</view>
<!-- 第二组表单 -->
<view class="form-card">
<view class="section-card">
<view class="card-header">
<view class="line"></view>
<text>经营区域信息</text>
</view>
<fui-form-item
required
asterisk
......@@ -393,24 +385,13 @@ function submit() {
:labelWeight="400"
labelWidth="auto"
>
<view class="time-input" @click="pageData.show.address = true">
<view class="picker-input" @click="pageData.show.address = true">
<text class="select-text" :class="{ placeholder: !pageData.form.provinceText }">
{{ pageData.form.provinceText || '请选择归属地区' }}
</text>
<fui-icon name="arrowright" :size="32" color="#ccc"></fui-icon>
</view>
</fui-form-item>
<!-- <view class="fui-input-wrapper" @click="pageData.show.address = true">
<fui-input
required
readonly
labelWidth="auto"
labelSize="28"
:labelWeight="400"
label="归属地区"
placeholder="请选择归属地区"
:value="pageData.form.provinceText"
/>
</view> -->
<fui-input
required
labelWidth="auto"
......@@ -429,24 +410,13 @@ function submit() {
:labelWeight="400"
labelWidth="auto"
>
<view class="time-input" @click="pageData.show.mainProducts = true">
<view class="picker-input" @click="pageData.show.mainProducts = true">
<text class="select-text" :class="{ placeholder: !pageData.form.growCropsText }">
{{ pageData.form.growCropsText || '请选择种植物种' }}
</text>
<fui-icon name="arrowright" :size="32" color="#ccc"></fui-icon>
</view>
</fui-form-item>
<!-- <view class="fui-input-wrapper" @click="pageData.show.mainProducts = true">
<fui-input
required
readonly
labelWidth="auto"
labelSize="28"
:labelWeight="400"
label="种植物种"
placeholder="请选择种植物种"
v-model="pageData.form.growCropsText"
/>
</view> -->
<fui-input
required
size="28"
......@@ -459,11 +429,14 @@ function submit() {
/>
</view>
<!-- 第三组表单 - 图片上传 -->
<view class="form-card">
<!-- 上传图片换行显示 -->
<view class="section-card">
<view class="card-header">
<view class="line"></view>
<text>农场展示图片</text>
</view>
<view class="form-item-block">
<view class="form-item-label required">示例图片</view>
<view class="upload-tips">建议上传农场实景照片,展示农场风貌</view>
<view class="block-upload">
<uni-file-picker
ref="uploadRef"
......@@ -476,8 +449,8 @@ function submit() {
</view>
</view>
<view class="submit-btn-box">
<fui-button :text="pageData.form.id ? '保存修改' : '添加'" bold radius="96rpx" @click="submit" />
<view class="submit-area">
<fui-button :text="pageData.form.id ? '保存修改' : '添加农场'" bold radius="100rpx" @click="submit" />
</view>
</fui-form>
</view>
......@@ -507,105 +480,64 @@ function submit() {
<style lang="scss" scoped>
.page {
background: #e8f5e9;
min-height: 100vh;
background-color: #f4f7f5;
font-family: 'DingTalk Sans', sans-serif;
}
.page-container {
padding: 28rpx;
}
.form-card {
background-color: #ffffff;
border-radius: 20rpx;
margin-bottom: 20rpx;
padding: 0;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.upload-title {
padding: 28rpx;
font-size: 28rpx;
color: #333333;
font-weight: 400;
}
.upload-content {
padding: 0 32rpx 32rpx;
}
.submit-btn-box {
padding: 32rpx 0;
}
:deep(.fui-button) {
border-color: #ff9800 !important;
background: #ff9800 !important;
}
:deep(.fui-input__wrap),
:deep(.fui-textarea__wrap) {
padding: 28rpx 32rpx;
// border-bottom: 1px solid #e5e5e5;
}
:deep(.fui-input__label),
:deep(.fui-textarea__label) {
font-size: 28rpx;
color: #333333;
min-width: 160rpx;
font-weight: 400;
}
:deep(.fui-input__self),
:deep(.fui-textarea__self) {
font-size: 28rpx;
color: #333333;
}
:deep(.fui-input__placeholder),
:deep(.fui-textarea__placeholder) {
color: #d0d0d0;
font-size: 28rpx;
.form-banner {
background-color: #5db66f;
padding: 60rpx 40rpx 100rpx;
color: #fff;
.title {
font-size: 48rpx;
font-weight: bold;
display: block;
letter-spacing: 2rpx;
}
.subtitle {
font-size: 26rpx;
opacity: 0.9;
margin-top: 12rpx;
display: block;
}
}
.form-card :deep(.fui-input__wrap:last-child),
.form-card :deep(.fui-textarea__wrap:last-child) {
border-bottom: none;
.form-content {
padding: 24rpx;
margin-top: -80rpx;
position: relative;
z-index: 10;
}
::v-deep .uni-input-placeholder {
font-size: 28rpx !important;
color: #999999 !important;
}
:deep(.uni-textarea-placeholder) {
font-size: 28rpx !important;
color: #999999 !important;
}
:deep(.fui-button) {
width: 690rpx;
border-color: #5db66f !important;
background: #5db66f !important;
}
.section-card {
background-color: #fff;
border-radius: 28rpx;
padding: 32rpx;
margin-bottom: 28rpx;
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.03);
// 移除fui-form的默认样式
:deep(.fui-form) {
background: transparent;
.card-header {
margin-bottom: 32rpx;
display: flex;
align-items: center;
.line {
width: 8rpx;
height: 32rpx;
background-color: #5db66f;
border-radius: 4rpx;
margin-right: 16rpx;
}
text {
font-size: 32rpx;
font-weight: bold;
color: #1a1a1a;
}
}
}
:deep(.fui-form__item) {
background: transparent;
border: none;
margin-bottom: 0;
padding: 0;
}
:deep(.fui-input__border-bottom) {
right: 32rpx !important;
}
.form-item-block {
padding: 24rpx 12rpx;
// border-bottom: 1px solid #e5e5e5;
padding: 24rpx 0;
&:last-child {
border-bottom: none;
......@@ -625,14 +557,18 @@ function submit() {
}
}
.upload-tips {
font-size: 22rpx;
color: #ccc;
margin-bottom: 20rpx;
}
.block-textarea {
width: 100%;
padding: 0rpx 12rpx !important;
}
.block-upload {
width: 100%;
padding: 0 20rpx;
:deep(.uni-file-picker__container) {
background-color: #fff !important;
border: unset !important;
......@@ -642,16 +578,86 @@ function submit() {
justify-content: left !important;
}
}
.picker-input {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
width: 100%;
}
.select-text {
font-size: 28rpx;
color: #333333;
padding: 0 20rpx;
flex: 1;
&.placeholder {
color: #999999;
}
}
.fui-input-wrapper {
cursor: pointer;
.submit-area {
padding: 60rpx 20rpx 100rpx;
:deep(.fui-button) {
background: #5db66f !important;
border-color: #5db66f !important;
box-shadow: 0 8rpx 24rpx rgba(93, 182, 111, 0.3);
}
}
:deep(.fui-form) {
background: transparent;
}
:deep(.fui-form__item) {
background: transparent;
border: none;
margin-bottom: 0;
padding: 0;
}
:deep(.fui-input__wrap),
:deep(.fui-textarea__wrap) {
padding: 28rpx 0;
}
:deep(.fui-input__label),
:deep(.fui-textarea__label) {
font-size: 28rpx;
color: #333333;
min-width: 160rpx;
font-weight: 400;
font-family: 'DingTalk Sans', system-ui !important;
}
:deep(.fui-input__self),
:deep(.fui-textarea__self) {
font-size: 28rpx;
color: #333333;
font-family: 'DingTalk Sans', system-ui !important;
}
:deep(.fui-input__placeholder),
:deep(.fui-textarea__placeholder) {
color: #d0d0d0;
font-size: 28rpx;
font-family: 'DingTalk Sans' !important;
}
::v-deep .uni-input-placeholder {
font-size: 28rpx !important;
color: #999999 !important;
font-family: 'DingTalk Sans' !important;
}
:deep(.uni-textarea-placeholder) {
font-size: 28rpx !important;
color: #999999 !important;
font-family: 'DingTalk Sans' !important;
}
:deep(.fui-button) {
font-family: 'DingTalk Sans' !important;
font-weight: bold !important;
}
</style>
</style>
\ No newline at end of file
......@@ -22,13 +22,33 @@
const page = reactive<Page>({
id: 'example-mapbox',
init: false,
mapInit: false,
loading: false,
mapReady: false,
requests: 0,
latest: null,
query: {},
})
const showDialog = ref(false)
onHide(() => {
page.init = false
page.mapInit = false
model.id = ''
model.name = ''
model.farmbaseInfo = {}
model.farmbaseInfoList = []
model.deviceTypeCount = []
})
onShow(() => {
// getFarmbaseInfoList()
if (!page.init) {
if (page.mapReady) {
initMapData()
} else {
refreshPageData()
}
}
})
const model = reactive({
......@@ -150,278 +170,250 @@
})
}
// 地图组件
const [registerMap, map] = useMapbox({
style: { zoom: 15, isImg: true },
onLoaded: async (data) => {
console.log('✨✨✨ Map Loaded', data)
async function fetchFarmInfo() {
const res = await NongchangAPI.farmsList()
if (!res.records || res.records.length === 0) {
throw new Error('未查询到农场信息')
}
return res.records[0]
}
// 查询农场数据
const res = await NongchangAPI.farmsList()
async function fetchFarmbaseList(farmId: string) {
const res = await farmbaseApi.list({ farmId })
return res.records || []
}
if (!res.records || res.records.length === 0) {
Message.toast('未查询到农场信息')
return
}
async function refreshPageData() {
if (page.init) return
// 获取第一个农场信息
const item = res.records?.[0]
try {
const item = await fetchFarmInfo()
model.id = item.id
model.name = item.farmName
model.description = item.description
// 设置页面标题
uni.setNavigationBarTitle({
title: item.farmName,
})
uni.setNavigationBarTitle({ title: item.farmName })
// 渲染农场和基地地块数据
let farmGeojson = item.geojson
// geojson 可能是字符串,需要解析
if (typeof farmGeojson === 'string') {
const farmbaseRecords = await fetchFarmbaseList(model.id)
model.farmbaseInfoList = farmbaseRecords
if (farmbaseRecords.length > 0) {
model.farmbaseInfo = farmbaseRecords[0]
}
getDeviceTypeCount()
page.init = true
} catch (error) {
Message.toast(error.message || '数据加载失败')
}
}
const colorPool = [
{ fill: '#e74c3c', line: '#e74c3c' },
{ fill: '#3498db', line: '#3498db' },
{ fill: '#2ecc71', line: '#2ecc71' },
{ fill: '#f1c40f', line: '#f1c40f' },
{ fill: '#9b59b6', line: '#9b59b6' },
{ fill: '#e67e22', line: '#e67e22' },
{ fill: '#1abc9c', line: '#1abc9c' },
{ fill: '#e91e63', line: '#e91e63' },
]
function renderFarmLayers(item: any, farmGeojson: any) {
if (!farmGeojson) {
console.warn('农场没有 geojson 数据')
return
}
const farmFeatures = farmGeojson.features ? farmGeojson.features : [farmGeojson]
for (const feature of farmFeatures) {
feature.properties = feature.properties || {}
feature.properties.farmName = item.farmName
}
addDefaultGeoJSONSource(map, `${page.id}-farm-plot`, farmFeatures)
map.addLayer({
type: 'fill',
id: `${page.id}-farm-plot-fill`,
source: `${page.id}-farm-plot`,
paint: {
'fill-color': '#5db66f',
'fill-opacity': 0.2,
'fill-outline-color': '#5db66f',
},
})
map.addLayer({
type: 'line',
id: `${page.id}-farm-plot-line`,
source: `${page.id}-farm-plot`,
layout: { 'line-join': 'round', 'line-cap': 'round' },
paint: { 'line-color': '#5db66f', 'line-width': 4 },
})
}
function renderBaseLayers(farmbaseRecords: any[]) {
for (let i = 0; i < farmbaseRecords.length; i++) {
const base = farmbaseRecords[i]
let geojson = base.geojson
if (typeof geojson === 'string') {
try {
farmGeojson = JSON.parse(farmGeojson)
geojson = JSON.parse(geojson)
} catch (e) {
console.error('解析农场 geojson 失败:', e)
farmGeojson = null
console.error(`解析基地 ${base.baseName} geojson 失败:`, e)
continue
}
}
if (farmGeojson) {
const farmFeatures = farmGeojson.features ? farmGeojson.features : [farmGeojson]
// 注入农场名称到每个 feature
for (const feature of farmFeatures) {
feature.properties = feature.properties || {}
feature.properties.farmName = item.farmName
}
addDefaultGeoJSONSource(map, `${page.id}-farm-plot`, farmFeatures)
// 添加农场填充层
map.addLayer({
type: 'fill',
id: `${page.id}-farm-plot-fill`,
source: `${page.id}-farm-plot`,
paint: {
'fill-color': '#5db66f',
'fill-opacity': 0.2,
'fill-outline-color': '#5db66f',
},
})
map.addLayer({
type: 'line',
id: `${page.id}-farm-plot-line`,
source: `${page.id}-farm-plot`,
layout: {
'line-join': 'round',
'line-cap': 'round',
},
paint: {
'line-color': '#5db66f',
'line-width': 4,
},
})
// 添加农场名称文字标签(已禁用)
// map.addLayer({
// type: 'symbol',
// id: `${page.id}-farm-label`,
// source: `${page.id}-farm-plot`,
// layout: {
// 'text-field': ['get', 'farmName'],
// 'text-size': 16,
// 'text-anchor': 'center',
// 'text-allow-overlap': true,
// },
// paint: {
// 'text-color': '#ffffff',
// 'text-halo-color': '#5db66f',
// 'text-halo-width': 2,
// },
// })
} else {
console.warn('农场没有 geojson 数据, item:', JSON.stringify(item)?.substring(0, 500))
if (!geojson) continue
const color = colorPool[i % colorPool.length]
const sourceId = `${page.id}-base-plot-${base.id}`
const features = geojson.features ? geojson.features : [geojson]
for (const feature of features) {
feature.properties = feature.properties || {}
feature.properties.baseName = base.baseName
}
// 查询农场基地信息
const farmbaseRes = await farmbaseApi.list({ farmId: model.id })
const farmbaseRecords = farmbaseRes.records || []
model.farmbaseInfoList = farmbaseRecords
addDefaultGeoJSONSource(map, sourceId, features)
// 预定义颜色池(每个基地循环使用)
const colorPool = [
{ fill: '#e74c3c', line: '#e74c3c' }, // 红
{ fill: '#3498db', line: '#3498db' }, // 蓝
{ fill: '#2ecc71', line: '#2ecc71' }, // 绿
{ fill: '#f1c40f', line: '#f1c40f' }, // 黄
{ fill: '#9b59b6', line: '#9b59b6' }, // 紫
{ fill: '#e67e22', line: '#e67e22' }, // 橙
{ fill: '#1abc9c', line: '#1abc9c' }, // 青
{ fill: '#e91e63', line: '#e91e63' }, // 粉
]
map.addLayer({
type: 'fill',
id: `${sourceId}-fill`,
source: sourceId,
paint: {
'fill-color': color.fill,
'fill-opacity': 0.5,
'fill-outline-color': color.line,
},
})
// 为每个基地创建独立的 source 和 layer
for (let i = 0; i < farmbaseRecords.length; i++) {
const base = farmbaseRecords[i]
let geojson = base.geojson
// geojson 可能是字符串,需要解析
if (typeof geojson === 'string') {
try {
geojson = JSON.parse(geojson)
} catch (e) {
console.error(`解析基地 ${base.baseName} geojson 失败:`, e)
continue
}
}
map.addLayer({
type: 'line',
id: `${sourceId}-line`,
source: sourceId,
layout: { 'line-join': 'round', 'line-cap': 'round' },
paint: { 'line-color': color.line, 'line-width': 3 },
})
if (!geojson) continue
map.addLayer({
type: 'symbol',
id: `${sourceId}-label`,
source: sourceId,
layout: {
'text-field': ['get', 'baseName'],
'text-size': 14,
'text-anchor': 'center',
'text-offset': [0, 0],
'text-allow-overlap': true,
},
paint: {
'text-color': '#ffffff',
'text-halo-color': color.line,
'text-halo-width': 2,
},
})
}
}
// 为当前基地分配颜色
const color = colorPool[i % colorPool.length]
const sourceId = `${page.id}-base-plot-${base.id}`
const fillLayerId = `${sourceId}-fill`
const lineLayerId = `${sourceId}-line`
const labelLayerId = `${sourceId}-label`
function flyToBaseCenter(firstBase: any) {
let firstGeojson = firstBase.geojson
if (typeof firstGeojson === 'string') {
try {
firstGeojson = JSON.parse(firstGeojson)
} catch (e) {
firstGeojson = null
}
}
// 处理 geojson,注入基地名称属性
const features = geojson.features ? geojson.features : [geojson]
for (const feature of features) {
feature.properties = feature.properties || {}
feature.properties.baseName = base.baseName
if (firstGeojson) {
const coords = firstGeojson.features?.[0]?.geometry?.coordinates?.[0]
if (coords && coords.length > 0) {
let minLon = Number.POSITIVE_INFINITY
let minLat = Number.POSITIVE_INFINITY
let maxLon = Number.NEGATIVE_INFINITY
let maxLat = Number.NEGATIVE_INFINITY
for (const c of coords) {
if (c[0] < minLon) minLon = c[0]
if (c[0] > maxLon) maxLon = c[0]
if (c[1] < minLat) minLat = c[1]
if (c[1] > maxLat) maxLat = c[1]
}
const center: [number, number] = [(minLon + maxLon) / 2, (minLat + maxLat) / 2]
baseCenterLonLat.value = center
map.flyTo({ center, zoom: 15, duration: 0 })
}
} else if (firstBase.longitude && firstBase.latitude) {
const center: [number, number] = [Number(firstBase.longitude), Number(firstBase.latitude)]
baseCenterLonLat.value = center
map.flyTo({ center, zoom: 15, duration: 0 })
}
}
// 添加 source
addDefaultGeoJSONSource(map, sourceId, features)
// 添加填充层(直接创建,不使用 addDefaultSplotLayer)
map.addLayer({
type: 'fill',
id: fillLayerId,
source: sourceId,
paint: {
'fill-color': color.fill,
'fill-opacity': 0.5,
'fill-outline-color': color.line,
},
})
async function initMapData() {
if (page.mapInit) return
// 添加边框层
map.addLayer({
type: 'line',
id: lineLayerId,
source: sourceId,
layout: {
'line-join': 'round',
'line-cap': 'round',
},
paint: {
'line-color': color.line,
'line-width': 3,
},
})
try {
page.loading = true
// 添加基地名称文字标签
map.addLayer({
type: 'symbol',
id: labelLayerId,
source: sourceId,
layout: {
'text-field': ['get', 'baseName'],
'text-size': 14,
'text-anchor': 'center',
'text-offset': [0, 0],
'text-allow-overlap': true,
},
paint: {
'text-color': '#ffffff',
'text-halo-color': color.line,
'text-halo-width': 2,
},
})
}
const item = await fetchFarmInfo()
model.id = item.id
model.name = item.farmName
model.description = item.description
// 将地图视角飞到第一个基地的上方
if (farmbaseRecords.length > 0) {
const firstBase = farmbaseRecords[0]
model.farmbaseInfo = firstBase
let firstGeojson = firstBase.geojson
if (typeof firstGeojson === 'string') {
try {
firstGeojson = JSON.parse(firstGeojson)
} catch (e) {
firstGeojson = null
}
}
if (firstGeojson) {
const coords = firstGeojson.features?.[0]?.geometry?.coordinates?.[0]
if (coords && coords.length > 0) {
let minLon = Number.POSITIVE_INFINITY
let minLat = Number.POSITIVE_INFINITY
let maxLon = Number.NEGATIVE_INFINITY
let maxLat = Number.NEGATIVE_INFINITY
for (const c of coords) {
if (c[0] < minLon) minLon = c[0]
if (c[0] > maxLon) maxLon = c[0]
if (c[1] < minLat) minLat = c[1]
if (c[1] > maxLat) maxLat = c[1]
}
const center: [number, number] = [(minLon + maxLon) / 2, (minLat + maxLat) / 2]
baseCenterLonLat.value = center
map.flyTo({
center,
zoom: 15,
duration: 0,
})
}
} else if (firstBase.longitude && firstBase.latitude) {
const center: [number, number] = [Number(firstBase.longitude), Number(firstBase.latitude)]
baseCenterLonLat.value = center
map.flyTo({
center,
zoom: 15,
duration: 0,
})
uni.setNavigationBarTitle({ title: item.farmName })
let farmGeojson = item.geojson
if (typeof farmGeojson === 'string') {
try {
farmGeojson = JSON.parse(farmGeojson)
} catch (e) {
console.error('解析农场 geojson 失败:', e)
farmGeojson = null
}
}
getDeviceTypeCount()
renderFarmLayers(item, farmGeojson)
// 如果农场没有 geojson 但有坐标,则使用坐标定位
if (!farmGeojson && item.longitude && item.latitude) {
model.lonlat = `${item.longitude},${item.latitude}`
} else if (!farmGeojson && !item.longitude) {
model.lonlat = '111.024108, 29.554847'
Message.toast('未设置农场坐标位置,已使用模拟位置数据')
}
const farmbaseRecords = await fetchFarmbaseList(model.id)
model.farmbaseInfoList = farmbaseRecords
// 渲染设备数据
renderBaseLayers(farmbaseRecords)
// 渲染设备数据
model.devices = [
turf.point([111.024108, 29.554847], {
name: '设备1',
description: '设备1描述',
icon: 'GD',
popup: `{{name}}`,
}),
turf.point([111.023139, 29.55539], {
name: '设备2',
description: '设备2描述',
icon: 'JCD',
popup: `{{name}}`,
}),
turf.point([111.024989, 29.555435], {
name: '设备3',
description: '设备3描述',
icon: 'BZ',
popup: `{{name}}`,
}),
turf.point([111.024108, 29.554847], { name: '设备1', description: '设备1描述', icon: 'GD', popup: '{{name}}' }),
turf.point([111.023139, 29.55539], { name: '设备2', description: '设备2描述', icon: 'JCD', popup: '{{name}}' }),
turf.point([111.024989, 29.555435], { name: '设备3', description: '设备3描述', icon: 'BZ', popup: '{{name}}' }),
]
addDefaultGeoJSONSource(map, `${page.id}-symbol`, model.devices)
addDefaultSymbolLayer(map, `${page.id}-symbol`, {
layout: {
'text-field': '',
'icon-image': ['get', 'icon'],
'icon-size': 1,
},
layout: { 'text-field': '', 'icon-image': ['get', 'icon'], 'icon-size': 1 },
})
if (farmbaseRecords.length > 0) {
model.farmbaseInfo = farmbaseRecords[0]
flyToBaseCenter(farmbaseRecords[0])
}
getDeviceTypeCount()
page.init = true
page.mapInit = true
} catch (error) {
Message.toast(error.message || '数据加载失败')
} finally {
page.loading = false
}
}
const [registerMap, map] = useMapbox({
style: { zoom: 15, isImg: true },
onLoaded: async (data) => {
console.log('✨✨✨ Map Loaded', data)
page.mapReady = true
await initMapData()
},
onSourceRequestHandle: () => {
page.requests--
......@@ -519,6 +511,11 @@
function onBackClick() {
uni.navigateBack({
delta: 1,
fail: () => {
uni.switchTab({
url: '/pages/nongchang/nongchang'
})
}
})
}
......@@ -679,7 +676,7 @@
</view>
</view>
</view>
<view class="box-3">
<view class="box-3" v-if="model.farmbaseInfoList && model.farmbaseInfoList.length > 0">
<!-- 基地切换 -->
<view class="box-3-tags">
<view
......@@ -693,7 +690,7 @@
</view>
</view>
</view>
<view class="box-3-info">
<view class="box-3-info" v-if="model.farmbaseInfo && model.farmbaseInfo.id">
<view class="box-3-info-item">
<view class="box-3-info-item-text-box">
<text class="box-3-info-item-text1">总面积: </text>
......@@ -728,12 +725,12 @@
</view>
</view>
</view>
<view class="box-4">
<view class="box-4" v-if="model.farmbaseInfoList && model.farmbaseInfoList.length > 0">
<view class="box-4-title">
<view class="box-4-title-text1"><text>基地设备</text></view>
<view class="box-4-title-text2" @click="showDialog = true"><text>+ 添加设备</text></view>
</view>
<view class="box-4-device">
<view class="box-4-device" v-if="model.deviceTypeCount && model.deviceTypeCount.length > 0">
<view
class="box-4-device-item"
v-for="(device, index) in model.deviceTypeCount"
......@@ -752,6 +749,9 @@
</view>
</view>
</view>
<view class="box-4-empty" v-else>
<text class="empty-text">暂无设备数据</text>
</view>
</view>
</view>
</view>
......@@ -1207,6 +1207,20 @@
}
}
}
.box-4-empty {
width: 100%;
padding: 60rpx 0;
text-align: center;
background-color: #fff;
border-radius: 18rpx;
margin-top: 10rpx;
.empty-text {
font-size: 28rpx;
color: #999;
}
}
}
}
}
......
<script setup lang="ts">
import { reactive } from 'vue'
import { reactive, getCurrentInstance } from 'vue'
import { onShow } from '@dcloudio/uni-app'
const instance = getCurrentInstance()
// import WeatherForecast from './components/WeatherForecast.vue'
import dayjs from 'dayjs'
import Navigate from '@/utils/page/navigate'
......@@ -454,7 +456,7 @@
pageData.agricultureClass.videoList.forEach((_, index) => {
if (index !== currentIndex) {
try {
const videoContext = uni.createVideoContext(`video${index}`)
const videoContext = uni.createVideoContext(`video${index}`, instance.proxy)
videoContext.pause()
} catch (error) {
console.log('停止视频失败:', error)
......@@ -469,7 +471,7 @@
}
function playVideo(index) {
const videoContext = uni.createVideoContext(`video${index}`)
const videoContext = uni.createVideoContext(`video${index}`, instance.proxy)
videoContext.requestFullScreen({ direction: 0 })
videoContext.play()
}
......@@ -477,7 +479,7 @@
function handleFullscreenChange(e: any, index) {
console.log(`视频${index}全屏状态改变`)
if (!e.detail.fullScreen) {
const videoContext = uni.createVideoContext(`video${index}`)
const videoContext = uni.createVideoContext(`video${index}`, instance.proxy)
videoContext.pause()
}
}
......@@ -494,12 +496,10 @@
}
onHide(() => {
// 停止所有其他视频的播放(只暂停,不重置位置)
pageData.agricultureClass.videoList.forEach((_, index) => {
try {
const videoContext = uni.createVideoContext(`video${index}`)
const videoContext = uni.createVideoContext(`video${index}`, instance.proxy)
videoContext.pause()
// 不重置到开头,保留播放位置
} catch (error) {
console.log('停止视频失败:', error)
}
......
......@@ -15,6 +15,7 @@ import { useUserStore } from '@/store/modules/user'
const userStore = useUserStore()
const dictStore = useDictStore()
const instance = getCurrentInstance()
const model = reactive({
// 湖南省人民政府
location: '112.982931,28.116698',
......@@ -452,7 +453,7 @@ function handleVideoPause(currentIndex) {
function handleFullscreenChange(e: any, index) {
console.log(`视频${index}全屏状态改变`)
if (!e.detail.fullScreen) {
const videoContext = uni.createVideoContext(`video${index}`)
const videoContext = uni.createVideoContext(`video${index}`, instance.proxy)
videoContext.pause()
}
}
......@@ -473,14 +474,14 @@ function handleMetadataLoaded(e: any,video:any) {
function handleVideoPlay(currentIndex) {
pageData.agricultureClass.videoList.forEach((_, index) => {
if (index !== currentIndex) {
const videoContext = uni.createVideoContext(`video${index}`)
const videoContext = uni.createVideoContext(`video${index}`, instance.proxy)
videoContext.pause()
}
})
}
function playVideo(index) {
const videoContext = uni.createVideoContext(`video${index}`)
const videoContext = uni.createVideoContext(`video${index}`, instance.proxy)
videoContext.requestFullScreen({ direction: 0 })
videoContext.play()
}
......@@ -508,21 +509,16 @@ function toNewsDetail(item) {
}
onHide(() => {
// 停止所有其他视频的播放(只暂停,不重置位置)
pageData.agricultureClass.videoList.forEach((_, index) => {
try {
const videoContext = uni.createVideoContext(`video${index}`)
const videoContext = uni.createVideoContext(`video${index}`, instance.proxy)
videoContext.pause()
// 不重置到开头,保留播放位置
} catch (error) {
console.log('停止视频失败:', error)
}
})
})
// 引入实例
const instance = getCurrentInstance();
const getDomDataSetFunc = (domName='',datasetName='index') =>{
return new Promise((resolve,reject)=>{
const queryDataget = uni.createSelectorQuery().in(instance.proxy);
......@@ -717,9 +713,9 @@ export default {
</view>
<view class="codefun-flex-row section_5">
<view v-show="item.status" v-for="item in pageData.serviceItems" :key="item.id"
class="codefun-flex-col codefun-items-center group_10 mt-5" @click="onMenuItemClick(item)">
class="codefun-flex-col codefun-items-center group_10" @click="onMenuItemClick(item)">
<image class="image_11" :src="item.icon_url" />
<text class="font_2 mt-11">{{ item.name }}</text>
<text class="font_2 mt-3">{{ item.name }}</text>
</view>
</view>
<swiper :autoplay="true" :interval="5000" :duration="500" :circular="true" :vertical="true"
......@@ -1164,7 +1160,8 @@ export default {
}
.group_10 {
margin-bottom: 15rpx;
margin-top: 5rpx;
margin-bottom: 18rpx;
flex: 1 1 173.5rpx;
.text_50 {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论